-
Notifications
You must be signed in to change notification settings - Fork 0
Refactoring JavaScript
Kyle Coberly edited this page Jul 8, 2017
·
3 revisions
- Refactoring changes code while ensuring behavior stays the same. Otherwise, you’re just changing code. The interfaces need to stay the same.
- Make the data independent from the program.
- CPS = “continual passing style”, keeps passing values through, no outside state
- Mispelling
- Short names and abbreviations
- Non-descriptive and general names
- Numbers in names
- Names that are used more than once
- Not CapitalCase for constructors
- Not camelCase for variables and methods
- Dead code
- Speculative code
- Comments - put tasks and to-dos elsewhere, keep doc-generating comments
- Whitespace,
EOL,EOF - Do-nothing code
- Logs and debugs
- Remove magic numbers and strings
- Break up long lines, use shorthand operators
- Inline short function calls
- Cache lookups
- Don’t hoist variable declarations, do hoist functions
- Remove variables with duplicate information
- Fix
self = thisstatements - Use
_for privacy, not a complicated pattern
- Use template strings over concatenation
- Remove magic strings
- Use regex to search strings
- Break up long lines on commas, or group them together
- Use
mapinstead of pushing into new arrays -
for ofloops are better thanforloops for arrays/objects -
forEachis preferable to for loops
- Use
Setfor all unique values - Use objects instead of arrays for named keys
- Use
Mapfor:- Not hierarchy/prototypes
- Elements are all similar
- Use
WeakSetandWeakMapfor- No sizing
- No iteration
- Use a bit field as an alternative to an array of booleans
- Subclass to avoid conditionals
- Unnecessarily nested hierarchy is hyperextension, fix it by pushing things higher up the hierarchy until the tests still pass
- Reduce arity (number of parameters) with partial application
- Should be a natural, fluid process
- Get away from “running the file” imperatively
- Wrap sequential statements in functions
- Inline if the function doesn’t really do anything
- Inlining variables makes extracting functions easier
- Take things out of the global namespace and put them in an object
- Extracting named functions may reduce the clarity of your code
Testing leads to confidence, confidence enables refactoring, refactoring leads to quality.
- SOLID
- DRY
- KISS
- GRASP (General Responsibility Assignment Software Pattern)
- YAGNI
- EVAN
- Extract functions and modules to simplify interfaces
- Verify code behavior through tests
- Avoid impure functions when possible
- Name variables and functions well
- Style guides
- Developer happiness meetings
- Pairing
- Code Review
- TDD / BDD
- Test frameworks
- Assertion library
- Domain-specific drivers
- Factories & fixtures
- Mock/stub libraries
- Build/Task/Packaging tools
- Loaders/Watchers
- Test Run Parallelizer
- CI Server
- Coverage Reporters
- Linters
- Logger/Debugger
- Staging/QA Servers
Quality might mean code/test coverage, low complexity, number of arguments to functions, low length of file. Tech debt is complexity and lack of coverage.
- Function bulk comes from lines of code and cyclomatic complexity (different paths through code, caused by conditionals)
- Inputs. Can be explicit, implicit (
this), non-local. Preference in that order. Bind or pass this. - Outputs. Don’t return
nullorundefined, don’t return different types out of the same function. - Side-effects. Minimize them.
Math.randomnew Date- Calls to external URLs
-
use strictspeeds up JavaScript, but takes features out. - Which “JavaScript”? JS Implementation -> Platform -> Framework -> Library
- You can use named parameters in options objects with destructuring.
-
Mapsuseget/set, can be converted to arrays, and are the same as hashes/dictionaries in other languages - Your first function in a promise chain must be thennable, but you can cheat with
Promise.resolve().then()
-
- A mock asserts; a stub does not.
- TDD is more like making soup than baking.
-
itdoesn’t need to be in adescribeor assert anything. - You can write your tests directly inside of the file being tested.
- Write tests for anything you need confidence in
- Factor out randomness by putting conditionals and ranges in the test
-
tapeis super easy JS testing - You can add functions to objects in tests, and then call them during the assertion
- You can call an async function in a test and just work with the result
- You’re already doing it
- It’s a necessary part of refactoring
- Makes working with a team easier
- Demonstrates functionality
- Verify behavior of 3rd party code
- Necessary for big upgrades
- Catches bugs earlier
- Smoothes out the dev cycle by eliminating tech debt
- Creates a tight feedback loop
- Manual
- Documented-manual (the “QA Plan”)
- Approval test (a human verifies the output, and their choice is saved)
- Unit test
- Non-functional test
- Performance
- Usability
- Play testing
- Security
- Accessibility
- Localization
- Feature test (for test-first)
- Regression test (reproduces bug)
- Characterization test (a feature test after the fact- if no side-effects, passing means done)
- Mutation testing (programmatically changes inputs to functions)
- Extract shared state & behavior to classes
- Only use default arguments if they are true defaults, not just common cases
- You can build hierarchies with:
- Classes (preferred)
- Object literals
- Factory functions
- Constructor functions
- You can make mixins with
Object.Assign(objects on right clobber objects on left)
Helps get rid of conditionals.
class SuperClass {
log(n){
this.specificMethod(n);
}
}
class aSubClass(){
specificMethod(n){}
}
class theSubClass(){
specificMethod(n){}
}- Pass function definition in constructor
- Can package all strategies together in an object
- Combinations of state & behavior bundled together
- Make an object that handles null and returns this, “”, false, etc.
- Generally mirrors the full API
- Alternative to null checks
- Add trait to something you don’t own- doesn’t modify original class.
- Can be used to convert null to null objects
- Use factory functions instead of constructors (has a return value, would never be instantiated)
function NewTrait(oldObject){
const newObject = Object.create(oldObject);
newObject.newTrait = "hey";
return newObject;
}- Converts 2 dissimilar interfaces.
- Maps interfaces together, but otherwise similar to decorator
- Decorator and adapter are both more complicated than changing underlying code
constructor(adapter){
this.newMethod = adapter.oldMethod;
}- Simplify an interface or create a DSL
- jQuery is a DOM facade
Cache results of expensive calculation in a lookup table
- Time and order makes something imperative
- Spreadsheet functions are a good example of declarative programming
- No variables
- No global state
- Functions must return
- Every if must have an else (otherwise would mutate state, side effect, throw)
- Function declarations have typed signatures
- Type-safety during compilation
- No null
- Side effects are difficult
- Functional purity
- Idempotence (same input gives you the same output)
- No side effects
- No race conditions (easy concurrency and scaling)
- Type safety == confidence
- No null checks
- The evaluation of an expression is the same as the value it produces (“referential transparency”)
- Works well with many cores and lots of memory
- Don’t do assignment (or reassignment) in conditionals or loops
- Don’t do destructive functions or mutate
-
functionName!For destruction,functionName?For boolean
-
- Minimize state/scope with IIFEs
- Get rid of conditionals w/
- Polymorphic subclasses
- Delegation of properties
- Extracting to separate functions
- Partial application - return a function (first argument applied)
- Compose functions out of other functions
- Currying - chain calls
- Ramda gives you a utility for it
- Save specific kinds of maps
- Point-free (no named parameters)
- You can memoize any function with Ramda because the results are idempotent
functionName :: InputType -> InputType -> OutputType
(functionInput -> functionOutput)
[arrayValue]
Can curry inputs to make new functions
- Too much frustration and excitement means that your feedback loop is too long.
- Make programming boring.
- Write badly to get to green, copy/paste code, but use tools to catch bad stuff, then refactor.
- Commit after every green.
Refactoring legacy code:
- Put JavaScript in it’s own file
- Put the project under version control
- Pick a testing strategy