Refactoring: What Is It and When Do I Need it?
As a regular reader of JetRuby Agency blog, you may well have seen the Refactoring section.
Whilst all of these articles make perfect sense to developers (trust us), it may seem to be of little use to business owners. We’ve created a short introductory article, to answer some of the questions you may have.
So, without any further ado, let’s get straight to it.
Question 1. What is refactoring?
The question is answered partially by the Wikipedia article on the subject:
Code refactoring is the process of restructuring existing computer code — changing the factoring — without changing its external behavior. Refactoring improves nonfunctional attributes of the software. Advantages include improved code readability and reduced complexity; these can improve source-codemaintainability and create a more expressive internal architecture or object model to improve extensibility.
We’ll try to skip the “boring” techie stuff this time and focus on what businesses actually need.
In more down-to-earth language, refactoring is the process of reorganizing the code base without introducing any new functionality.
WHAT? Basically you want to do something with the code (which I don’t see) and spend precious development time WITHOUT adding new features? You’ve got to be kidding, right?
We will answer this very question a bit later. Thanks for not switching the channel.
Question 2. How do I know if my application needs refactoring?
There are 3 major reasons to perform a refactoring:
1. Code architecture is poor. This happens when the application is delivered in as-fast-as-it-gets manner and the developer(s) and the product owner do not care about the integrity and maintainability of the solution. The situation may get even worse, if the scope of the project has switched heavily, or there were dozens of freelancers, who had been working on the solution at different times (no conventions, no code style, e.t.c.)
2. The project didn’t leave proof-of-concept status in time. It’s not a secret, that some projects succeed, some fail. Smart product owners need to make sure that “killer features” do work as intended, and can be demonstrated to investors and stakeholders. Or, as product companies do, they validate their ideas with narrow focus groups. In each case they assign limited resources to check whether the idea or approach actually works in the real world. As soon as you get a “yes” to this question (it does work), you should budget for the following jobs and (this is an important piece) actually plan or adjust solution architecture. Otherwise you may end up building a skyscraper on quicksand.
3. Technology stack is outdated. This happens too. Technologies evolve rapidly, businesses need cross-platform solutions, mobile adaptation, etc. Take a website from 1999 and try to add some fancy ReactJS interactivity to it. You may try to make it work, but basically the amount of time spent can not be justified by the expected result.
When this news arrives from the development company, it’s cause for tears, right?
Well, yes and no.
You could ignore the information, skip the third-party code audit and just proceed with the current approach.
Here’s list of the possible issues you could run into:
1. The application gets buggy. As time goes, some bugs are solved just to be replaced by new ones. Why? Because developers are fixing issues, but it becomes visible only afterwards that the developers have broken something else. The bigger the application, the more code linesit has , the more intersections there are, the more non-conventional solutions are present — the worse the situation usually is.
2. The application is slow. As you introduce new functionality, you inevitably add code. As you add more data to DB, you inevitably add to the size. So, in the early stages, problems with server-side or client-side performance may not be evident. But as time goes on, they may become pretty obvious.
3. Development takes too long even for simple features. This is related to the previous point. To deliver a stable product, the developer needs to address the issues outside of his scope, basically he does refactoring while at the same time delivering the feature. This works, but lacks a systematic approach. And it isn’t exactly transparent to you as the person paying the bills, right?
4. Some features will not be completed. Again, related to both of the above. Introducing a new component or feature may just not be worth the effort. Imagine you get an estimate and it says that it will take 2 full days just to make a simple dropdown menu. The development company would typically just say “you don’t need it”, or something like that. We at JetRuby are not used to saying “no” to clients, but an informed decision leading to zero result hardly sweetens the pill.
Question 3. What is the expected result?
The benefits of a proper refactoring are directly related to the issues we intend to solve:
- Easier expansion of the system? Better collaboration of developers? Less time on discussions, more time on good solutions. Money saved — [check-icon].
- Easier implementation of changes? Less bugs? Less QA? Money saved — [check-icon].
- Better performance of the application on client side? Happier clients — [check-icon].
- Better performance of the server side? Cheaper AWS plan — [check-icon].
- Less money spent on support and development in future. [check-icon].
- The list goes on, actually…
Basically, you will be able to solve most, if not all, of the aforementioned problems.
Question 4. How much does it cost?
The short answer — it depends.
The longer version is… anywhere between ~100 hours and 2000+.
Small projects may take 50–150 hours to update the technology stack.
Bigger projects with changes on the backend and the frontend may easily take 500h+.
Or it may take 10–15 hours, just because we need to get rid of the non-optimal solutions and move on, evolve and fundraise.
It’s important to properly inform the developer(s) with your plans — close and distant. This is a major question from day one. Sometimes (quite often), it makes sense to combine refactoring and implementing new features, when you are sure that older functionality will be changed soon.
When in doubt, you may order a third-party code audit of a proven company. These don’t come cheap, but they may be perfectly justified, when we are talking about 1000+h scopes.
Question 5. How do I avoid refactoring? I don’t want to slip on this banana.
Rule 1. Find the right guys. Development company should be professional, transparent, and with a good reputation. You know one, right? ;)
Rule 2. Be clear with your wishes. It may be challenging to comprehend one’s dream in it’s full glory and put it into SRS. Read how SCRUM works and what a product owner does.
Rule 3. Be specific about your target audience, stakeholders, future plans. The bigger the picture we share, the less room for false assumptions and the higher the chance to meet expectations.
Rule 4. Inform your development company when there’s a major switch in roles on the project, high probability of approach change or scope being cancelled. Remember, we run the project and it should perfectly match the product; you are the person that knows it best.
Rule 5. Do not postpone Quality Assurance to “when the time comes”. Accumulated issues are harder to fix. Also, they will take time (and money) to be solved and you basically won’t be getting new functionality at this point. You end up paying for what you considered to be solved already. You get frustration, and your audience has a gap in releases (assuming they are OK with a buggy app in the first place).
Rule 6. Plan ahead. You will be needing mobile adaptation at some point (very likely). Maybe you will be needing a mobile app? Or several apps and user roles. Try to imagine how it looks in the end, then transfer your vision to the right guys. This enables for effective architecture planning and choosing the proper tech stack.
OK, you’ve got the news. Refactoring is needed.
Bad part: New investment needed, sometimes (often) unplanned.
Good part: The product will become better, in so many ways. We hope you can see it now.
Piece of advice — don’t treat this as an expense. This is a smart investment. Deciding otherwise, you may end up spending twice as much later or worse, hitting a dead end.
We intend to publish more articles “translating” what software development firms do into something more to the ground, allowing for better transparency and more productive collaboration.
Please give us a couple dozen “claps” if you feel this article has been helpful.
To install, first make sure you have a working copy of the latest stable version of Node.js. You can then install CoffeeScript globally with npm:
This will make the and commands available globally.
If you are using CoffeeScript in a project, you should install it locally for that project so that the version of CoffeeScript is tracked as one of your project’s dependencies. Within that project’s folder:
The and commands will first look in the current folder to see if CoffeeScript is installed locally, and use that version if so. This allows different versions of CoffeeScript to be installed globally and locally.
If you plan to use the option (see Transpilation) you will need to also install either globally or locally, depending on whether you are running a globally or locally installed version of CoffeeScript.
Once installed, you should have access to the command, which can execute scripts, compile files into , and provide an interactive REPL. The command takes the following options:
|Launch an interactive CoffeeScript session to try short snippets. Identical to calling with no arguments.|
|Watch files for changes, rerunning the specified command when any file is updated.|
|Parses the code as Literate CoffeeScript. You only need to specify this when passing in code directly over stdio, or using some sort of extension-less file name.|
|Compile and print a little snippet of CoffeeScript directly from the command line. For example:|
|the given module before starting the REPL or evaluating the code given with the flag.|
|Suppress the “Generated by CoffeeScript” header.|
|The executable has some useful options you can set, such as , , , and . Use this flag to forward options directly to Node.js. To pass multiple flags, use multiple times.|
|Instead of parsing the CoffeeScript, just lex it, and print out the token stream. Used for debugging the compiler.|
|Instead of compiling the CoffeeScript, just lex and parse it, and print out the parse tree. Used for debugging the compiler.|
- Compile a directory tree of files in into a parallel tree of files in :
- Watch a file for changes, and recompile it every time the file is saved:
- Concatenate a list of files into a single script:
- Print out the compiled JS from a one-liner:
- All together now, watch and recompile an entire project as you work on it:
- Start the CoffeeScript REPL ( to exit, for multi-line):
To use , see Transpilation.
If you’d like to use Node.js’ CommonJS to CoffeeScript files, e.g. , you must first “register” CoffeeScript as an extension:
If you want to use the compiler’s API, for example to make an app that compiles strings of CoffeeScript on the fly, you can the full module:
The method has the signature where is a string of CoffeeScript code, and the optional is an object with some or all of the following properties:
- , boolean: if true, a source map will be generated; and instead of returning a string, will return an object of the form .
- , boolean: if true, output the source map as a base64-encoded string in a comment at the bottom.
- , string: the filename to use for the source map. It can include a path (relative or absolute).
- , boolean: if true, output without the top-level function safety wrapper.
- , boolean: if true, output the header.
- , object: if set, this must be an object with the options to pass to Babel. See Transpilation.
From the root of your project:
Transpiling with the CoffeeScript compiler
To make things easy, CoffeeScript has built-in support for the popular Babel transpiler. You can use it via the command-line option or the Node API option. To use either, must be installed in your project:
Or if you’re running the command outside of a project folder, using a globally-installed module, needs to be installed globally:
By default, Babel doesn’t do anything—it doesn’t make assumptions about what you want to transpile to. You need to provide it with a configuration so that it knows what to do. One way to do this is by creating a file in the folder containing the files you’re compiling, or in any parent folder up the path above those files. (Babel supports other ways, too.) A minimal file would be just . This implies that you have installed :
See Babel’s website to learn about presets and plugins and the multitude of options you have. Another preset you might need is if you’re using JSX with React (JSX can also be used with other frameworks).
Once you have and (or other presets or plugins) installed, and a file (or other equivalent) in place, you can use to pipe CoffeeScript’s output through Babel using the options you’ve saved.
If you’re using CoffeeScript via the Node API, where you call with a string to be compiled and an object, the key of the object should be the Babel options:
First, the basics: CoffeeScript uses significant whitespace to delimit blocks of code. You don’t need to use semicolons to terminate expressions, ending the line will do just as well (although semicolons can still be used to fit multiple expressions onto a single line). Instead of using curly braces to surround blocks of code in functions, if-statements, switch, and try/catch, use indentation.
You don’t need to use parentheses to invoke a function if you’re passing arguments. The implicit call wraps forward to the end of the line or block expression.
Functions are defined by an optional list of parameters in parentheses, an arrow, and the function body. The empty function looks like this:
Functions may also have default values for arguments, which will be used if the incoming argument is missing ().
Multiline strings are allowed in CoffeeScript. Lines are joined by a single space unless they end with a backslash. Indentation is ignored.
Block strings, delimited by or , can be used to hold formatted or indentation-sensitive text (or, if you just don’t feel like escaping quotes and apostrophes). The indentation level that begins the block is maintained throughout, so you can keep it all aligned with the body of your code.
Double-quoted block strings, like other double-quoted strings, allow interpolation.
Objects and Arrays
CoffeeScript has a shortcut for creating objects when you want the key to be set with a variable of the same name.
Lexical Scoping and Variable Safety
The CoffeeScript compiler takes care to make sure that all of your variables are properly declared within lexical scope — you never need to write yourself.
Notice how all of the variable declarations have been pushed up to the top of the closest scope, the first time they appear. is not redeclared within the inner function, because it’s already in scope; within the function, on the other hand, should not be able to change the value of the external variable of the same name, and therefore has a declaration of its own.
Because you don’t have direct access to the keyword, it’s impossible to shadow an outer variable on purpose, you may only refer to it. So be careful that you’re not reusing the name of an external variable accidentally, if you’re writing a deeply nested function.
Although suppressed within this documentation for clarity, all CoffeeScript output (except in files with or statements) is wrapped in an anonymous function: . This safety wrapper, combined with the automatic generation of the keyword, make it exceedingly difficult to pollute the global namespace by accident. (The safety wrapper can be disabled with the option, and is unnecessary and automatically disabled when using modules.)
If you’d like to create top-level variables for other scripts to use, attach them as properties on ; attach them as properties on the object in CommonJS; or use an statement. If you’re targeting both CommonJS and the browser, the existential operator (covered below), gives you a reliable way to figure out where to add them: .
Since CoffeeScript takes care of all variable declaration, it is not possible to declare variables with ES2015’s or . This is intentional; we feel that the simplicity gained by not having to think about variable declaration outweighs the benefit of having three separate ways to declare variables.
If, Else, Unless, and Conditional Assignment
/ statements can be written without the use of parentheses and curly brackets. As with functions and other block expressions, multi-line conditionals are delimited by indentation. There’s also a handy postfix form, with the or at the end.
Splats, or Rest Parameters/Spread Syntax
Splats also let us elide array elements…
…and object properties.
In ECMAScript this is called spread syntax, and has been supported for arrays since ES2015 but is coming soon for objects. Until object spread syntax is officially supported, the CoffeeScript compiler outputs the same polyfill as Babel’s rest spread transform; but once it is supported, we will revise the compiler’s output. Note that there are very subtle differences between the polyfill and the current proposal.
Loops and Comprehensions
Most of the loops you’ll write in CoffeeScript will be comprehensions over arrays, objects, and ranges. Comprehensions replace (and compile into) loops, with optional guard clauses and the value of the current array index. Unlike for loops, array comprehensions are expressions, and can be returned and assigned.