Refactoring JavaScript - KeynesYouDigIt/Knowledge GitHub Wiki
Refactoring
- 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
Rename variables to remove
- 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
Remove useless code
- Dead code
- Speculative code
- Comments - put tasks and to-dos elsewhere, keep doc-generating comments
- Whitespace,
EOL
,EOF
- Do-nothing code
- Logs and debugs
Variable Refactoring
- 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 = this
statements - Use
_
for privacy, not a complicated pattern
String Refactoring
- Use template strings over concatenation
- Remove magic strings
- Use regex to search strings
Array Refactoring
- Break up long lines on commas, or group them together
- Use
map
instead of pushing into new arrays for of
loops are better thanfor
loops for arrays/objectsforEach
is preferable to for loops
Array/Object Alternatives:
- Use
Set
for all unique values - Use objects instead of arrays for named keys
- Use
Map
for:- Not hierarchy/prototypes
- Elements are all similar
- Use
WeakSet
andWeakMap
for- No sizing
- No iteration
- Use a bit field as an alternative to an array of booleans
OOP Refactoring
- Subclass to avoid conditionals
- Unnecessarily nested hierarchy is hyperextension, fix it by pushing things higher up the hierarchy until the tests still pass
FP
- Reduce arity (number of parameters) with partial application
Extracting and inlining functions
- 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
Quality
Testing leads to confidence, confidence enables refactoring, refactoring leads to quality.
Quality Tools:
- 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.
Parts of a function
- 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
null
orundefined
, don’t return different types out of the same function. - Side-effects. Minimize them.
Sources of variation
Math.random
new Date
- Calls to external URLs
JavaScript
use strict
speeds up JavaScript, but takes features out.- Which “JavaScript”? JS Implementation -> Platform -> Framework -> Library
- You can use named parameters in options objects with destructuring.
Maps
useget
/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()
Testing
-
- A mock asserts; a stub does not.
- TDD is more like making soup than baking.
it
doesn’t need to be in adescribe
or 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
tape
is 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
Why testing
- 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
Types of tests:
- 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)
OOP
- 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)
OOP Patterns
Template Method
Helps get rid of conditionals.
class SuperClass {
log(n){
this.specificMethod(n);
}
}
class aSubClass(){
specificMethod(n){}
}
class theSubClass(){
specificMethod(n){}
}
Strategy
- Pass function definition in constructor
- Can package all strategies together in an object
State
- Combinations of state & behavior bundled together
Null Object
- Make an object that handles null and returns this, “”, false, etc.
- Generally mirrors the full API
- Alternative to null checks
Decorator
- 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;
}
Adapter
- 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;
}
Facade
- Simplify an interface or create a DSL
- jQuery is a DOM facade
Memo
Cache results of expensive calculation in a lookup table
Functional Programming
- Time and order makes something imperative
- Spreadsheet functions are a good example of declarative programming
Restrictions:
- 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
Benefits:
- 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
Basics
- 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
Advanced Basics
- 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
Hindley-Milner Type System
functionName :: InputType -> InputType -> OutputType
(functionInput -> functionOutput)
[arrayValue]
Can curry inputs to make new functions
Workflow
- 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