Whether using jslint, jshint, eslint or jscs; automating the removal of simple bugs is a critical step. It can and probably should be broken up into smaller chunks by applying rules a few at time. A few rules that will help and drive future refactoring and are highly recommended are strict mode, requiring semicolons, not mixing tab and spaces, deleting unused variables, requiring block statements and type equality. Once syntax changes are implemented, enforce them with hooks to the code repository and/or part of the build process. Git, Subversion and Mercurial all implement hooks that are perfect for this. Your builds in grunt can also deal with git hooks too.
Closures are not the final form but as a professional, pragmatic developer, the goal is to make incremental steps that can be released. By not allowing global scope inside the closure, you can emulate imports (the arguments) and exports (global namespace additions). The code should split up into 1 file per globally name-spaced “exported” object or function. Surprisingly, the code still hasn’t logically changed ( unless you were abusing a syntax error ). There could be a lot of files now if splitting them up when closuring (recommended), where before there only had a few.
This necessary step has the potential clean up code dramatically without requiring logical changes to the code.
myFunc doesn’t exist,
Func might! A simple search/replace on the code is not enough. So be careful when removing functions that might not be used. It is easy to delete code that is used without finding a place it’s being used. Doing a ‘debugger;’ statement and then running the application’s functionality would help reassure that code is not being triggered (assuming you can trigger all of the functionality easily). Or add logging notes and watch for usage in the logs. Also if code is running prior to initialization, then regardless if no other code is importing it, then it cannot be safely removed until debugged.
Before doing any large scale, breaking changes, pay attention to the production ready code. Logging for errors and having a visual layer to perceive errors over time is something any size team will benefit from. When changing out large swaths of code, there will always be a breakage somewhere you aren’t expecting and tracking the errors will highlight that. NewRelic provides common tools to get the job done, but this is also one of those areas where rolling your own isn’t too difficult.
Using the statistical analysis, you should be able to find high cyclomatic complex functions. Begin turning these into multiple functions that call each other, rather than a single large function. Try to find the generic patterns in the code so generic, testable functions can be created. Make sure to write tests too. Don’t worry just yet about completely refactoring the logic because it’s still not going to be entirely clear what the code is attempting to do in all cases. Focus on making small testable functions. Take notes for odd code or code that you feel needs revisited. It will take a long time to get to small, purposeful functions, but keep at it.
The code is probably modular in a sense at this point, but it’s important to apply 2015+ best practices. Look at the ES6 module structure and look at using transpilers like Babel or SystemJS to rewrite your code in a module format. Still valid, but less future proof, would be using CommonJS (Node style modules) or Require with a Browserify step. In any case, going from script loading to module loading is a key performance and structural change to the code. It’s not a step that can be done quickly and without error; which is why all the previous work was done. However, once using best practices with modules, the code becomes much more manageable and adaptable for any future efforts.
The code is modular, small, syntax free and known. The last set of tasks is to basically re-think the application. The nitty-gritty implementation details should be rethought for clarity and performance. Are the events properly bound in the DOM? Is a pubsub model in place for non-DOM eventing? Is the data immutable? The amount of work could be endless, but it’s important to understand that these questions are only now possible due to the heavy work done already. The argument is no longer revolving around making better code, it’s now making smarter code. It’s a continuous process, but we’ve hit all the high level architecture changes that provide a framework for the really deep questions on performance and application hierarchy. Go forth and improve.