├── kiss ├── program.js └── program.test.js ├── .github ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── lod ├── solution │ ├── LawOfDemeter.js │ └── LawOfDemeter.test.js ├── assignment.md ├── LawOfDemeter.js └── LawOfDemeter.test.js ├── .babelrc ├── .eslintignore ├── solid ├── ocp │ ├── constants.js │ ├── ocp.md │ ├── Composition.js │ ├── Registry.js │ ├── Cart.js │ └── program.test.js ├── lsp │ ├── constants.js │ ├── Geometry.js │ └── program.test.js ├── solution │ ├── lsp │ │ ├── constants.js │ │ ├── Geometry.js │ │ └── program.test.js │ └── srp │ │ ├── program.js │ │ └── program.test.js ├── dip │ ├── Db.js │ ├── InitializeDb.js │ ├── Repository2.js │ ├── Repository.js │ ├── UserModels.js │ └── Repository.test.js ├── isp │ ├── program.js │ └── program.test.js └── srp │ ├── program.solution.js │ ├── program.js │ ├── assignment.md │ └── program.test.js ├── docs ├── demeter.pdf ├── solid │ ├── dip.pdf │ ├── isp.pdf │ ├── lsp.pdf │ ├── ocp.pdf │ └── srp.pdf ├── yagni.md └── pragmaticprogrammertips.md ├── SUMMARY.md ├── data ├── soldiers.csv └── users.js ├── .editorconfig ├── dry ├── program.js ├── assignment.md ├── program.test.js └── solution │ ├── program.test.js │ └── program.js ├── .gitignore ├── LICENSE ├── .eslintrc.js ├── package.json └── README.md /kiss/program.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lod/solution/LawOfDemeter.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lod/solution/LawOfDemeter.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0", "react-hmre"] 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /mongodbData 3 | /static/build 4 | /models/users.js 5 | /icomoon -------------------------------------------------------------------------------- /solid/ocp/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | ADD_CART: 'ADD_CART' 5 | }; 6 | -------------------------------------------------------------------------------- /docs/demeter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/pragmatic-programmer-and-clean-code-workshop/HEAD/docs/demeter.pdf -------------------------------------------------------------------------------- /docs/solid/dip.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/pragmatic-programmer-and-clean-code-workshop/HEAD/docs/solid/dip.pdf -------------------------------------------------------------------------------- /docs/solid/isp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/pragmatic-programmer-and-clean-code-workshop/HEAD/docs/solid/isp.pdf -------------------------------------------------------------------------------- /docs/solid/lsp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/pragmatic-programmer-and-clean-code-workshop/HEAD/docs/solid/lsp.pdf -------------------------------------------------------------------------------- /docs/solid/ocp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/pragmatic-programmer-and-clean-code-workshop/HEAD/docs/solid/ocp.pdf -------------------------------------------------------------------------------- /docs/solid/srp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbelmont/pragmatic-programmer-and-clean-code-workshop/HEAD/docs/solid/srp.pdf -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | * [Introduction](/README.md) 2 | 3 | * [YAGNI](docs/yagni.md) 4 | 5 | * [Pragmatic Programmer Tips](docs/pragmaticprogrammertips.md) 6 | -------------------------------------------------------------------------------- /data/soldiers.csv: -------------------------------------------------------------------------------- 1 | John Rambo,Army,Sergeant First Class 2 | Chuck Norris,Air Force,Tech Sergeant 3 | John Doe,Marines,Private 4 | Luke Cage,Marines,Sergeant 5 | Guy Dude,Navy,Petty Officer 6 | -------------------------------------------------------------------------------- /solid/lsp/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rectangle: { 3 | width: 4, 4 | height: 5 5 | }, 6 | square: 5, 7 | circle: { 8 | PI: Math.PI, 9 | radius: 4 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /solid/solution/lsp/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rectangle: { 3 | width: 4, 4 | height: 5 5 | }, 6 | square: 5, 7 | circle: { 8 | PI: Math.PI, 9 | radius: 4 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /solid/dip/Db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Db { 4 | insert() {} 5 | update() {} 6 | delete() {} 7 | retrieve() {} 8 | } 9 | 10 | function Database() {} 11 | 12 | Database.prototype.insert = function() {}; 13 | Database.prototype.update = function() {}; 14 | Database.prototype.delete = function() {}; 15 | Database.prototype.retrieve = function() {}; 16 | 17 | module.exports = { 18 | Db, 19 | Database 20 | }; 21 | -------------------------------------------------------------------------------- /kiss/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | const td = require('testdouble'); 3 | 4 | test.before('setup', t => { 5 | t.pass(true); 6 | }); 7 | 8 | test.after('teardown', t => { 9 | t.pass(true); 10 | }); 11 | 12 | test('cars should have be vehicles', assert => { 13 | const {Car} = require('./program'); 14 | const expected = { 15 | make: 'Ford', 16 | model: 'Explorer' 17 | }; 18 | const actual = Car.info(); 19 | assert.is(actual, expected, `should return ${expected}`); 20 | }); 21 | -------------------------------------------------------------------------------- /solid/ocp/ocp.md: -------------------------------------------------------------------------------- 1 | ## Open/Closed Principle 2 | 3 | [Open/Closed Principle](https://en.wikipedia.org/wiki/Open/closed_principle) 4 | 5 | In object-oriented programming, the open/closed principle states: 6 | - "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" 7 | - that is, such an entity can allow its behaviour to be extended without modifying its source code. 8 | - The name open/closed principle has been used in two ways. 9 | - Both ways use inheritance to resolve the apparent dilemma, but the goals, techniques, and results are different. 10 | 11 | -------------------------------------------------------------------------------- /solid/lsp/Geometry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Rectangle(width, height) { 4 | this.width = width; 5 | this.height = height; 6 | } 7 | 8 | Rectangle.prototype.area = function() { 9 | return this.width * this.height; 10 | }; 11 | 12 | function Square(side) { 13 | Rectangle.call(this, side, side); 14 | } 15 | 16 | Square.prototype = Object.create(Rectangle.prototype); 17 | 18 | function Circle(radius) { 19 | Rectangle.call(this, radius, radius); 20 | } 21 | 22 | Circle.prototype = Object.create(Rectangle.prototype); 23 | 24 | module.exports = { 25 | Rectangle, 26 | Square, 27 | Circle 28 | }; 29 | -------------------------------------------------------------------------------- /solid/ocp/Composition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const projector = (state, fn) => { 4 | return fn(state); 5 | }; 6 | 7 | const partialApplication = (fn, ...args) => 8 | (...otherArgs) => { 9 | return fn.apply(this, Array.prototype.concat.call(...args, ...otherArgs)); 10 | }; 11 | 12 | const partialApplicationFromRight = (fn, ...args) => 13 | (...otherArgs) => { 14 | return fn.apply(this, Array.prototype.concat.call(...otherArgs, ...args)); 15 | }; 16 | 17 | exports.partialApply = partialApplication; 18 | exports.partialApplicationFromRight = partialApplicationFromRight; 19 | exports.projector = projector; 20 | -------------------------------------------------------------------------------- /dry/program.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function uglyAverage() { 4 | var numbers = [1, 2, 3, 4, 5]; 5 | 6 | var sum = 0, i; 7 | for (i = 0; i < numbers.length; i++) { 8 | sum += numbers[i]; 9 | } 10 | return sum / numbers.length; 11 | } 12 | 13 | function uglyStandardDeviation(numbers) { 14 | let sum = 0; 15 | for (let i = 0; i < numbers.length; i++) { 16 | sum += numbers[i]; 17 | } 18 | const avg = sum / numbers.length; 19 | 20 | let summation = 0; 21 | for (let i = 0; i < numbers.length; i++) { 22 | summation += Math.pow(Math.abs(numbers[i] - avg), 2); 23 | } 24 | return Number(Math.sqrt(summation / numbers.length).toFixed(2)); 25 | } 26 | 27 | module.exports = { 28 | uglyAverage, 29 | uglyStandardDeviation 30 | }; -------------------------------------------------------------------------------- /lod/assignment.md: -------------------------------------------------------------------------------- 1 | # Law of Demeter 2 | 3 | * Each unit should have only limited knowledge about other units: only units "closely" related to the current unit. 4 | * Each unit should only talk to its friends; don't talk to strangers. 5 | * Only talk to your immediate friends. 6 | 7 | [Law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter) 8 | 9 | 1. An object can call its own methods 10 | 2. it's okay to call methods on objects passed in to our method 11 | 3. it's okay to call methods on any objects we create 12 | 4. any directly held component objects 13 | 14 | The Law of Demeter for functions requires that a method M of an object O may only invoke the methods of the following kinds of objects: 15 | 1. O itself 16 | 2. M's parameters 17 | 3. Any objects created/instantiated within M 18 | 4. O's direct component objects 19 | 5. A global variable, accessible by O, in the scope of M 20 | -------------------------------------------------------------------------------- /docs/yagni.md: -------------------------------------------------------------------------------- 1 | # YAGNI 2 | 3 | **You aren't gonna need it** 4 | 5 | This is a mantra from Extreme Programming usually tied with agile software teams. 6 | It essentially means implementing features that you anticipate needing in the future. 7 | Martin Fowler discusses this concept in detail in the blog post [YAGNI](https://martinfowler.com/bliki/Yagni.html) 8 | 9 | YAGNI argues that you should not implement features that you anticipate needing. 10 | 11 | Building Features today for the future can be costly: 12 | 1. You devoted dev time for something that is not feasible and developer time is money. 13 | 2. You have to maintain this new feature in tandem with other features. 14 | 3. You falsely assume requirements that are wrong and you need to rewrite. 15 | 4. You are adding complexity to a code base for a perceived requirement. 16 | 5. You should wait until the feature is truly needed implementing it prematurely. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Environment Variables 40 | /.env 41 | 42 | # MongoDb data 43 | /mongodbData 44 | 45 | # vscode 46 | /.vscode 47 | 48 | # security certs 49 | /ca 50 | 51 | /static/build 52 | 53 | /__tests__/__snapshots__ -------------------------------------------------------------------------------- /solid/isp/program.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Square() {} 4 | 5 | Square.prototype.setSide = function(side) { 6 | this.side = side; 7 | }; 8 | 9 | Square.prototype.area = function() { 10 | return Math.pow(this.side, 2); 11 | }; 12 | 13 | function Rectangle() {} 14 | 15 | Rectangle.prototype.setWidth = function(width) { 16 | this.width = width; 17 | }; 18 | 19 | Rectangle.prototype.setHeight = function(height) { 20 | this.height = height; 21 | }; 22 | 23 | Rectangle.prototype.area = function() { 24 | return this.width * this.height; 25 | }; 26 | 27 | function Circle() {} 28 | 29 | Circle.prototype.setPI = function(PI) { 30 | this.PI = PI; 31 | }; 32 | 33 | Circle.prototype.setRadius = function(radius) { 34 | this.radius = radius; 35 | }; 36 | 37 | Circle.prototype.area = function() { 38 | return (this.PI * Math.pow(this.radius, 2)).toFixed(4); 39 | }; 40 | 41 | exports.Square = Square; 42 | exports.Rectangle = Rectangle; 43 | exports.Circle = Circle; 44 | -------------------------------------------------------------------------------- /dry/assignment.md: -------------------------------------------------------------------------------- 1 | ## DRY—Don’t Repeat Yourself 2 | 3 | ### Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. 4 | 5 | ****How does Duplication Arise?**** 6 | 7 | ### Imposed Duplication. 8 | 9 | *Developers feel they have no choice - the environment seems to require duplication* 10 | 11 | ### Inadvertent duplication. 12 | 13 | *Developers don't realize that they are duplicating information.* 14 | 15 | ### Impatient duplication. 16 | 17 | *Developers get lazy and duplicate because it seems easy.* 18 | 19 | ### Interdeveloper duplication. 20 | 21 | *Multiple people on a team duplicate a piece of information.* 22 | 23 | ### Exercise instructions 24 | 25 | *1. Open program.js* 26 | 27 | *2. Study the ugly average implementation and think why it is not dry.* 28 | 29 | *3. Fix failing tests by correcting implementation and read TODOS in each tests.* 30 | 31 | ###### The testing framework ava.js only reports the failing assertion but not all of them like tape.js and mocha.js so be mindful of this. 32 | -------------------------------------------------------------------------------- /solid/solution/srp/program.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const {join} = require('path'); 5 | 6 | const readSoldiers = () => { 7 | return new Promise((resolve, reject) => { 8 | fs.readFile(join(__dirname, '../../../data/soldiers.csv'), (err, data) => { 9 | if (err) { 10 | reject(err); 11 | } 12 | const soldiers = data.toString().trim().split('\n'); 13 | resolve(soldiers); 14 | }); 15 | }); 16 | }; 17 | 18 | const formatSoldiers = (soldiers) => { 19 | return soldiers.map(soldier => soldier.split(',')).map((field) => { 20 | return { 21 | name: field[0], 22 | rank: field[1], 23 | branch: field[2] 24 | }; 25 | }); 26 | }; 27 | 28 | const writeSoldiers = (soldiers) => { 29 | return fs.writeFile(join(__dirname, 'soldiers.json'), JSON.stringify(soldiers), (err) => { 30 | if (err) { 31 | throw err; 32 | } 33 | }); 34 | }; 35 | 36 | exports.readSoldiers = readSoldiers; 37 | exports.formatSoldiers = formatSoldiers; 38 | exports.writeSoldiers = writeSoldiers; 39 | -------------------------------------------------------------------------------- /solid/solution/lsp/Geometry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Rectangle(width, height) { 4 | this.width = width; 5 | this.height = height; 6 | } 7 | 8 | Rectangle.prototype.area = function() { 9 | return this.width * this.height; 10 | }; 11 | 12 | function Square(side) { 13 | Rectangle.call(this, side, side); 14 | } 15 | 16 | Square.prototype = Object.create(Rectangle.prototype); 17 | 18 | function Circle(radius) { 19 | Rectangle.call(this, radius, radius); 20 | } 21 | 22 | Circle.prototype = Object.create(Rectangle.prototype); 23 | 24 | function Shape() {} 25 | 26 | Shape.prototype.area = function() {}; 27 | 28 | function Circle2() {} 29 | 30 | Circle2.prototype = Object.create(Shape.prototype); 31 | 32 | Circle2.prototype.setRadius = function(radius) { 33 | this.radius = radius; 34 | }; 35 | 36 | Circle2.prototype.setPI = function(PI) { 37 | this.PI = PI; 38 | }; 39 | 40 | Circle2.prototype.area = function() { 41 | return this.PI * Math.pow(this.radius, 2); 42 | }; 43 | 44 | module.exports = { 45 | Rectangle, 46 | Square, 47 | Circle, 48 | Circle2 49 | }; 50 | -------------------------------------------------------------------------------- /lod/LawOfDemeter.js: -------------------------------------------------------------------------------- 1 | function Demeter(soldier) { 2 | this.name = soldier.name || ''; 3 | this.rank = soldier.rank || 'private'; 4 | this.specialty = soldier.specialty || []; 5 | this.years = soldier.years || 0; 6 | this.job = soldier.job || 'firefighter'; 7 | this.getInformation = function(newSoldier) { 8 | return Object.assign( 9 | {}, 10 | { name: this.name, rank: this.rank, specialty: this.specialty, years: this.years }, 11 | { exercise: newSoldier.exercise, branch: newSoldier.branch } 12 | ); 13 | }; 14 | this.civilianPlan = { 15 | printPlan: function() { 16 | return `Civilian Job Plan: ${this.job}`; 17 | }.bind(this) 18 | }; 19 | } 20 | 21 | Demeter.prototype.getSoldier = function() { 22 | return { 23 | name: this.name, 24 | rank: this.rank, 25 | specialty: this.specialty 26 | }; 27 | }; 28 | 29 | Demeter.prototype.soldierStats = function(newSoldier) { 30 | return { 31 | height: newSoldier.height, 32 | weight: newSoldier.weight, 33 | gender: newSoldier.gender, 34 | age: newSoldier.age 35 | }; 36 | }; 37 | 38 | exports.Demeter = Demeter; 39 | -------------------------------------------------------------------------------- /solid/ocp/Registry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Registry(defaultValue) { 4 | this._defaultValue = defaultValue; 5 | this._values = Object.create(null); 6 | } 7 | 8 | Registry.prototype.getState = function(name) { 9 | var value; 10 | if (Object.prototype.hasOwnProperty.call(this._values, name)){ 11 | value = this._values[name]; 12 | } else { 13 | value = this._defaultValue; 14 | } 15 | return value; 16 | }; 17 | 18 | Registry.prototype.registerState = function (name, value) { 19 | this._values[name] = value; 20 | }; 21 | 22 | class CreateStore { 23 | constructor(reducers, preloadedState) { 24 | this.reducers = reducers || []; 25 | this.preloadedState = preloadedState; 26 | } 27 | 28 | getPreloadedState() { 29 | return this.preloadedState; 30 | } 31 | 32 | getNewState(state, action) { 33 | return this.reducers.reduce((state, reducer) => { 34 | return reducer.getNewState(state, action); 35 | }, state); 36 | } 37 | 38 | register(reducer) { 39 | this.reducers.push(reducer); 40 | } 41 | } 42 | 43 | module.exports = { 44 | Registry, 45 | CreateStore 46 | }; 47 | -------------------------------------------------------------------------------- /solid/srp/program.solution.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const {join} = require('path'); 5 | 6 | const readSoldiers = (cb) => { 7 | return fs.readFile(join(__dirname, '../../data/soldiers.csv'), (err, data) => { 8 | if (err) { 9 | return cb(err); 10 | } 11 | cb(null, data); 12 | }); 13 | }; 14 | 15 | const formatSoldiers = (soldiers) => { 16 | return soldiers.map(soldier => soldier.split(',')).map((field) => { 17 | return { 18 | name: field[0], 19 | rank: field[2], 20 | branch: field[1] 21 | }; 22 | }); 23 | }; 24 | 25 | const writeSoldiers = (soldiers, cb) => { 26 | return fs.writeFile(join(__dirname, 'soldiers.json'), JSON.stringify(soldiers), (err) => { 27 | if (err) { 28 | return cb(err); 29 | } 30 | return cb(null, { status: 'created'}); 31 | }); 32 | }; 33 | 34 | const removeSoldiers = (file, cb) => { 35 | return fs.unlinkSync(file, (err) => { 36 | if (err) { 37 | return cb(err); 38 | } 39 | }); 40 | }; 41 | 42 | module.exports = { 43 | readSoldiers, 44 | formatSoldiers, 45 | writeSoldiers, 46 | removeSoldiers 47 | }; 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Code Craftsmanship Saturdays 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended" 5 | ], 6 | "parserOptions": { 7 | "ecmaVersion": 2016, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": true 11 | } 12 | }, 13 | "parser": "babel-eslint", 14 | "rules": { 15 | "strict": 0, 16 | // enable additional rules 17 | "indent": ["error", 2], 18 | "linebreak-style": ["error", "unix"], 19 | "quotes": ["error", "single"], 20 | "semi": ["error", "always"], 21 | 22 | // override default options for rules from base configurations 23 | "comma-dangle": ["error", "never"], 24 | "no-cond-assign": ["error", "always"], 25 | 26 | // disable rules from base configurations 27 | "no-console": "off", 28 | 29 | "react/jsx-uses-react": "error", 30 | "react/jsx-uses-vars": "error", 31 | }, 32 | "env": { 33 | "browser": true, 34 | "node": true, 35 | "shelljs": true, 36 | "jest": true, 37 | "es6": true, 38 | "commonjs": true, 39 | "mongo": true 40 | }, 41 | "plugins": [ 42 | "react", 43 | "jsx-a11y", 44 | "import" 45 | ] 46 | }; -------------------------------------------------------------------------------- /solid/isp/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('setup', assert => { 4 | assert.end(); 5 | }); 6 | 7 | test('test area of a square', assert => { 8 | const {Square} = require('./program'); 9 | const side = 5; 10 | const square = new Square(); 11 | square.setSide(side); 12 | const actual = square.area(); 13 | const expected = side * side; 14 | assert.equal(actual, expected, `area of square should be ${expected}`); 15 | assert.end(); 16 | }); 17 | 18 | test('test area of Rectangle', assert => { 19 | const {Rectangle} = require('./program'); 20 | const width = 4, height = 5; 21 | const rectangle = new Rectangle(); 22 | rectangle.setWidth(width); 23 | rectangle.setHeight(height); 24 | const actual = rectangle.area(); 25 | const expected = width * height; 26 | assert.equal(actual, expected, `area of rectangle with width of ${width} and height of ${height} should be ${expected}`); 27 | assert.end(); 28 | }); 29 | 30 | test('test area of circle', assert => { 31 | const {Circle} = require('./program'); 32 | const PI = Math.PI, radius = 5; 33 | const circle = new Circle(); 34 | circle.setPI(PI); 35 | circle.setRadius(radius); 36 | const actual = circle.area(); 37 | const expected = (PI * Math.pow(radius, 2)).toFixed(4); 38 | assert.equal(actual, expected, `area of circle with radius of ${radius} should be ${expected}`); 39 | assert.end(); 40 | }); 41 | 42 | test('teardown', assert => { 43 | assert.end(); 44 | }); 45 | -------------------------------------------------------------------------------- /solid/ocp/Cart.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Cart(registry, cart) { 4 | this.cart = cart || []; 5 | 6 | this.showCart = function() { 7 | return new ShoppingCart(cart); 8 | }; 9 | 10 | this.addCart = function(item) { 11 | this.cart.push(item); 12 | }.bind(this); 13 | 14 | const cartRegistry = new registry(cart); 15 | cartRegistry.registerState('showCart', this.showCart); 16 | cartRegistry.registerState('addCart', this.addCart); 17 | 18 | return cartRegistry; 19 | } 20 | 21 | function ShoppingCart(cart) { 22 | this.cart = cart; 23 | } 24 | 25 | const {CreateStore} = require('./Registry'); 26 | const {ADD_CART} = require('./constants'); 27 | 28 | class Cart2 extends CreateStore { 29 | constructor(reducers, preloadedState) { 30 | super(reducers, preloadedState); 31 | } 32 | getNewState(state, action) { 33 | const { 34 | type, 35 | items 36 | } = action; 37 | if (type == ADD_CART) { 38 | return [ 39 | ...state, 40 | items 41 | ]; 42 | } else { 43 | return state; 44 | } 45 | } 46 | } 47 | 48 | const { 49 | projector 50 | } = require('./Composition'); 51 | 52 | const Cart3 = (state) => { 53 | return projector(state, (state) => { 54 | return [].concat([], state); 55 | }); 56 | }; 57 | 58 | const CreateStore2 = (oldState, state) => { 59 | return () => { 60 | return [].concat.call([], oldState, state); 61 | }; 62 | }; 63 | 64 | 65 | module.exports = { 66 | Cart, 67 | Cart2, 68 | Cart3, 69 | CreateStore2 70 | }; 71 | -------------------------------------------------------------------------------- /solid/dip/InitializeDb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nano = require('nano')('http://127.0.0.1:5984/'); 4 | const winston = require('winston'); 5 | 6 | const usermodels = require('./UserModels'); 7 | 8 | function createDbConnection({ dbName = 'pragmaticprogrammer', name = 'users' } = {}) { 9 | return new Promise((resolve) => { 10 | nano.db.create(dbName, (err) => { 11 | if (!err) { 12 | const couchDBName = nano.use(dbName); 13 | return insertInitialDocument({ dbName: couchDBName, name }) 14 | .then(() => { 15 | resolve(retrieveDocument({ dbName: couchDBName , name })); 16 | }); 17 | } else { 18 | const db = nano.use(dbName); 19 | resolve(retrieveDocument({ dbName: db, name })); 20 | } 21 | }); 22 | }); 23 | } 24 | 25 | function retrieveDocument({dbName, name}) { 26 | return new Promise((resolve, reject) => { 27 | dbName.get(name, (err, body) => { 28 | if (!err) { 29 | resolve(body); 30 | } 31 | reject(err); 32 | }); 33 | }); 34 | } 35 | 36 | function insertInitialDocument({dbName, name}) { 37 | return new Promise((resolve, reject) => { 38 | dbName.insert(usermodels, name, (err, body) => { 39 | if (!err) { 40 | winston.info(`Created ${name} table: ${usermodels}`); 41 | resolve(body); 42 | } 43 | winston.log('error', 'Database Connection Error', {err}); 44 | reject(usermodels); 45 | }); 46 | }); 47 | } 48 | 49 | function dbActions({ dbName = 'pragmaticprogrammer', name = 'users' } = {}) { 50 | return createDbConnection({dbName, name}); 51 | } 52 | 53 | dbActions(); 54 | -------------------------------------------------------------------------------- /dry/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | 3 | test('Test ugly average function', t => { 4 | const {uglyAverage} = require('./program'); 5 | t.truthy(uglyAverage, 'uglyAverage does exist'); 6 | 7 | // calling uglyAverage returns hard-coded value 8 | const actual = uglyAverage(); 9 | const expected = 10; // TODO: Please correct expected by looking at hardcoded values in program.js. 10 | t.is(actual, expected, `uglyAverage should return a number but not ${expected}`); 11 | }); 12 | 13 | test('Implement average function', t => { 14 | const {average} = require('./program'); 15 | 16 | // TODO: Add average implementation 17 | t.truthy(average, 'average does exist'); 18 | 19 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 20 | const actual = average(numbers); 21 | const expected = 5.5; 22 | t.is(actual, expected, `average should return ${expected}`); 23 | }); 24 | 25 | test('Test ugly standardDeviation', t => { 26 | const {uglyStandardDeviation} = require('./program'); 27 | 28 | t.truthy(uglyStandardDeviation, 'uglyStandardDeviation does exist'); 29 | 30 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 31 | 32 | const actual = uglyStandardDeviation(numbers); 33 | const expected = 2.87; 34 | t.is(actual, expected, `standardDeviation returns ${expected}`); 35 | }); 36 | 37 | test('Implement standardDeviation', t => { 38 | // TODO: Add either a standardDeviation function or JavaScript Class 39 | // The name is arbitrary feel free to change it, I did in the solution. 40 | const { 41 | standardDeviation 42 | } = require('./program'); 43 | 44 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 45 | const actual = standardDeviation(numbers); 46 | const expected = 2.87; 47 | t.is(actual, expected, `standardDeviation returns ${expected}`); 48 | }); -------------------------------------------------------------------------------- /solid/lsp/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | 3 | test('test area of Rectangle', assert => { 4 | const {Rectangle} = require('./Geometry'); 5 | const { 6 | rectangle 7 | } = require('./constants'); 8 | const width = rectangle.width || 0; 9 | const height = rectangle.height || 0; 10 | const rect = new Rectangle(width, height); 11 | const actual = rect.area(); 12 | const expected = 20; 13 | assert.is(actual, expected, `area of rectangle with width of ${width} and height of ${height}`); 14 | }); 15 | 16 | test('test area of square', assert => { 17 | const {Square} = require('./Geometry'); 18 | const { 19 | square 20 | } = require('./constants'); 21 | const newSquare = new Square(square); 22 | const actual = newSquare.area(); 23 | const expected = 25; 24 | assert.is(actual, expected, `area of square with side of ${square} should have area of ${expected}`); 25 | }); 26 | 27 | test('test area of circle', assert => { 28 | const {Circle} = require('./Geometry'); 29 | const { 30 | circle 31 | } = require('./constants'); 32 | const PI = circle.PI; 33 | const radius = circle.radius; 34 | const newCircle = new Circle(radius); 35 | const actual = newCircle.area(); 36 | const expected = PI * Math.pow(radius, 2); 37 | assert.not(actual, expected, `area of circle should return ${expected} instead of ${actual}`); 38 | }); 39 | 40 | test('test liskov substitution principle with by using generic Shape', assert => { 41 | const {Circle} = require('./Geometry'); 42 | const { 43 | circle 44 | } = require('./constants'); 45 | const PI = circle.PI; 46 | const radius = circle.radius; 47 | const newCircle = new Circle(); 48 | newCircle.setRadius(radius); 49 | newCircle.setPI(PI); 50 | const actual = newCircle.area(); 51 | const expected = PI * Math.pow(radius, 2); 52 | assert.is(actual, expected, `area of circle should return correct area of ${expected}`); 53 | }); 54 | -------------------------------------------------------------------------------- /solid/solution/lsp/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | 3 | test('test area of Rectangle', assert => { 4 | const {Rectangle} = require('./Geometry'); 5 | const { 6 | rectangle 7 | } = require('./constants'); 8 | const width = rectangle.width || 0; 9 | const height = rectangle.height || 0; 10 | const rect = new Rectangle(width, height); 11 | const actual = rect.area(); 12 | const expected = 20; 13 | assert.is(actual, expected, `area of rectangle with width of ${width} and height of ${height}`); 14 | }); 15 | 16 | test('test area of square', assert => { 17 | const {Square} = require('./Geometry'); 18 | const { 19 | square 20 | } = require('./constants'); 21 | const newSquare = new Square(square); 22 | const actual = newSquare.area(); 23 | const expected = 25; 24 | assert.is(actual, expected, `area of square with side of ${square} should have area of ${expected}`); 25 | }); 26 | 27 | test('test area of circle', assert => { 28 | const {Circle} = require('./Geometry'); 29 | const { 30 | circle 31 | } = require('./constants'); 32 | const PI = circle.PI; 33 | const radius = circle.radius; 34 | const newCircle = new Circle(radius); 35 | const actual = newCircle.area(); 36 | const expected = PI * Math.pow(radius, 2); 37 | assert.not(actual, expected, `area of circle should return ${expected} instead of ${actual}`); 38 | }); 39 | 40 | test('test liskov substitution principle with by using generic Shape', assert => { 41 | const {Circle2} = require('./Geometry'); 42 | const { 43 | circle 44 | } = require('./constants'); 45 | const PI = circle.PI; 46 | const radius = circle.radius; 47 | const newCircle = new Circle2(); 48 | newCircle.setRadius(radius); 49 | newCircle.setPI(PI); 50 | const actual = newCircle.area(); 51 | const expected = PI * Math.pow(radius, 2); 52 | assert.is(actual, expected, `area of circle should return correct area of ${expected}`); 53 | }); 54 | -------------------------------------------------------------------------------- /solid/srp/program.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const {join} = require('path'); 5 | 6 | const SOLDIER_CSV = join(__dirname, '../../data/soldiers.csv'); 7 | const SOLDIER_JSON = join(__dirname, 'soldiers.json'); 8 | 9 | const readSoldiersFormatSoldiersAndWriteSoldiers = () => { 10 | return fs.readFile(SOLDIER_CSV, 'utf8', (err, data) => { 11 | if (err) { 12 | throw err; 13 | } 14 | const soldiers = data 15 | .trim() 16 | .split('\n') 17 | .map(soldier => soldier.split(',')) 18 | .map((field) => { 19 | return { 20 | name: field[0], 21 | rank: field[1], 22 | branch: field[2] 23 | }; 24 | }); 25 | return fs.writeFile(SOLDIER_JSON, JSON.stringify(soldiers), (err) => { 26 | if (err) { 27 | throw err; 28 | } 29 | }); 30 | }); 31 | }; 32 | 33 | const readSoldiers = (cb) => { 34 | return fs.readFile(SOLDIER_CSV, (err, data) => { 35 | if (err) { 36 | throw cb(err); 37 | } 38 | return cb(data); 39 | }); 40 | }; 41 | 42 | const formatSoldiers = (soldiers) => { 43 | return soldiers 44 | .map(soldier => soldier.split(',')) 45 | .map((field) => { 46 | return { 47 | name: field[0], 48 | rank: field[1], 49 | branch: field[2] 50 | }; 51 | }); 52 | }; 53 | 54 | const writeSoldiers = (soldiers, cb) => { 55 | return fs.writeFile(SOLDIER_JSON, JSON.stringify(soldiers), (err, data) => { 56 | if (err) { 57 | return cb(err); 58 | } 59 | return cb(null, data); 60 | }); 61 | }; 62 | 63 | const removeSoldiers = (file, cb) => { 64 | return fs.unlink(SOLDIER_JSON, (err) => { 65 | if (err) { 66 | return cb(err); 67 | } 68 | return cb('Removed File'); 69 | }); 70 | }; 71 | 72 | module.exports = { 73 | readSoldiersFormatSoldiersAndWriteSoldiers, 74 | readSoldiers, 75 | formatSoldiers, 76 | writeSoldiers, 77 | removeSoldiers 78 | }; 79 | -------------------------------------------------------------------------------- /solid/srp/assignment.md: -------------------------------------------------------------------------------- 1 | ## Single Responsibility principle 2 | 3 | **[Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle)** 4 | 5 | The single responsibility principle states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. 6 | All its services should be narrowly aligned with that responsibility. 7 | Robert C. Martin expresses the principle as follows: 8 | 9 | **A class should have only one reason to change.** 10 | 11 | **[Full Stack JavaScript](http://thefullstack.xyz/solid-javascript/)** 12 | There is strong corollary with Unix philosophy to do one thing and do it well 13 | 14 | Every function you write should do exactly one thing. It should have one clearly defined goal. 15 | 16 | You’ll be surprised at the number of times you would like your function to do more than "one thing". 17 | 18 | You’ll also struggle repeatedly with defining what the "one thing" you want to do is. 19 | 20 | How to spot clear violations to SRP? 21 | 22 | 1. Calling a function such as getAverageAndComputeStandardDeviationAndFormat is clearly doing too many things. 23 | * A function like this should be broken into multiple parts. 24 | 2. For each function that you create, think about whether some parts would be better if you extracted them into another smaller function. 25 | 3. After you have created your function, look through it again and see how many reusable functions that you can extract. 26 | 27 | Open program.js and look at the current implement and see if you can make functions that have a single purpose and still accomplish the same task. 28 | I created a set of empty function for you to use. 29 | Check out the documentation on fs and specifically 30 | [fs.readFile](https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback) 31 | [fs.writeFile](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback) 32 | [fs.unlink](https://nodejs.org/api/fs.html#fs_fs_unlink_path_callback) 33 | -------------------------------------------------------------------------------- /solid/dip/Repository2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nano = require('nano')('http://127.0.0.1:5984/'); 4 | const {Database} = require('./Db'); 5 | 6 | function Repository2 () {} 7 | 8 | Repository2.prototype = Object.create(Database.prototype); 9 | 10 | Repository2.prototype.insert = function(dbName, name, body) { 11 | return new Promise((resolve, reject) => { 12 | const couchDBName = nano.use(dbName); 13 | return this.insertDoc({ dbName: couchDBName, name, body }) 14 | .then(() => { 15 | resolve(this.retrieveDoc({ dbName: couchDBName , name })); 16 | }) 17 | .catch(err => { 18 | reject(err); 19 | }); 20 | }); 21 | }; 22 | 23 | Repository2.prototype.insertDoc = function({dbName, name, body}) { 24 | return new Promise((resolve, reject) => { 25 | dbName.insert(body, name, (err, body) => { 26 | if (!err) { 27 | resolve(body); 28 | } else { 29 | reject(err); 30 | } 31 | }); 32 | }); 33 | }; 34 | 35 | Repository2.prototype.retrieveDoc = function({dbName, name}) { 36 | return new Promise((resolve, reject) => { 37 | dbName.get(name, (err, body) => { 38 | if (!err) { 39 | resolve(body); 40 | } 41 | reject(err); 42 | }); 43 | }); 44 | }; 45 | 46 | Repository2.prototype.update = function(dbName, name, body) { 47 | return new Promise((resolve, reject) => { 48 | dbName.insert(body, name, (err, body) => { 49 | if (!err) { 50 | resolve(body); 51 | } else { 52 | reject(err); 53 | } 54 | }); 55 | }); 56 | }; 57 | 58 | Repository2.prototype.delete = function(dbName, name) { 59 | const couchDBName = nano.use(dbName); 60 | return this.retrieveDoc({dbName: couchDBName, name}) 61 | .then(body => { 62 | if (body) { 63 | const { 64 | _rev 65 | } = body; 66 | couchDBName.destroy(name, _rev, (err, body) => { 67 | if (!err) { 68 | return body; 69 | } 70 | throw err; 71 | }); 72 | } 73 | }); 74 | }; 75 | 76 | Repository2.prototype.retrieveDocument = function({dbName, name}) { 77 | const couchDBName = nano.use(dbName); 78 | return new Promise((resolve, reject) => { 79 | couchDBName.get(name, (err, body) => { 80 | if (!err) { 81 | resolve(body); 82 | } 83 | reject(err); 84 | }); 85 | }); 86 | }; 87 | 88 | exports.Repository2 = Repository2; 89 | -------------------------------------------------------------------------------- /solid/dip/Repository.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nano = require('nano')('http://127.0.0.1:5984/'); 4 | const {Db} = require('./Db'); 5 | 6 | class Repository extends Db { 7 | insert(dbName, name, body) { 8 | return new Promise((resolve, reject) => { 9 | const couchDBName = nano.use(dbName); 10 | return this.insertDoc({ dbName: couchDBName, name, body }) 11 | .then(() => { 12 | resolve(this.retrieveDoc({ dbName: couchDBName , name })); 13 | }) 14 | .catch(err => { 15 | reject(err); 16 | }); 17 | }); 18 | } 19 | 20 | insertDoc({dbName, name, body}) { 21 | return new Promise((resolve, reject) => { 22 | dbName.insert(body, name, (err, body) => { 23 | if (!err) { 24 | resolve(body); 25 | } else { 26 | reject(err); 27 | } 28 | }); 29 | }); 30 | } 31 | 32 | retrieveDoc({dbName, name}) { 33 | return new Promise((resolve, reject) => { 34 | dbName.get(name, (err, body) => { 35 | if (!err) { 36 | resolve(body); 37 | } 38 | reject(err); 39 | }); 40 | }); 41 | } 42 | 43 | update({dbName, name, body}) { 44 | return this.retrieveDocument({ dbName, name}) 45 | .then(result => { 46 | const rev = result['_rev']; 47 | const id = result['_id']; 48 | const newerBody = Object.assign({}, body, { _rev: rev, _id: id }); 49 | const couchDBName = nano.use(dbName); 50 | return couchDBName.insert(newerBody, (err, body) => { 51 | if (err) { 52 | throw err; 53 | } 54 | return body; 55 | }); 56 | }); 57 | } 58 | 59 | delete({dbName, name}) { 60 | const couchDBName = nano.use(dbName); 61 | return this.retrieveDoc({dbName: couchDBName, name}) 62 | .then(body => { 63 | if (body) { 64 | const { 65 | _rev 66 | } = body; 67 | couchDBName.destroy(name, _rev, (err, body) => { 68 | if (!err) { 69 | return body; 70 | } 71 | throw err; 72 | }); 73 | } 74 | }); 75 | } 76 | 77 | retrieveDocument({dbName, name}) { 78 | const couchDBName = nano.use(dbName); 79 | return new Promise((resolve, reject) => { 80 | couchDBName.get(name, (err, body) => { 81 | if (!err) { 82 | resolve(body); 83 | } 84 | reject(err); 85 | }); 86 | }); 87 | } 88 | } 89 | 90 | module.exports = { 91 | Repository 92 | }; 93 | -------------------------------------------------------------------------------- /lod/LawOfDemeter.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('test object can call its own methods', assert => { 4 | const Demeter = require('./LawOfDemeter').Demeter; 5 | const expected = { 6 | name: 'Rambo', 7 | rank: 'Sergeant', 8 | specialty: ['guns', 'explosives', 'hand-to-hand combat', 'tanks', 'helicopters', 'tactics', 'being a badass'] 9 | }; 10 | const demeter = new Demeter(expected); 11 | const actual = demeter.getSoldier(); 12 | assert.deepEqual(actual, expected, `should return ${expected}`); 13 | assert.end(); 14 | }); 15 | 16 | test('test calling methods on objects passed in to a method', assert => { 17 | const Demeter = require('./LawOfDemeter').Demeter; 18 | const expected = { 19 | height: 74.25, 20 | weight: 200, 21 | gender: 'male', 22 | age: 18 23 | }; 24 | const soldier = { 25 | name: 'Rambo', 26 | rank: 'Sergeant', 27 | specialty: ['guns', 'explosives', 'hand-to-hand combat', 'tanks', 'helicopters', 'tactics', 'being a badass'] 28 | }; 29 | const demeter = new Demeter(soldier); 30 | const actual = demeter.soldierStats(expected); 31 | assert.deepEqual(actual, expected, `should return ${expected}`); 32 | assert.end(); 33 | }); 34 | 35 | test('test call methods on any objects we create', assert => { 36 | const Demeter = require('./LawOfDemeter').Demeter; 37 | const soldier = { 38 | name: 'Rambo', 39 | rank: 'Sergeant', 40 | specialty: ['guns', 'explosives', 'hand-to-hand combat', 'tanks', 'helicopters', 'tactics', 'being a badass'], 41 | years: 5 42 | }; 43 | const demeter = new Demeter(soldier); 44 | const actual = demeter.getInformation({ exercise: 'pushups', branch: 'Army' }); 45 | const expected = { 46 | name: 'Rambo', 47 | rank: 'Sergeant', 48 | specialty: ['guns', 'explosives', 'hand-to-hand combat', 'tanks', 'helicopters', 'tactics', 'being a badass'], 49 | years: 5, 50 | exercise: 'pushups', 51 | branch: 'Army' 52 | }; 53 | assert.deepEqual(actual, expected, `should return ${expected}`); 54 | assert.end(); 55 | }); 56 | 57 | test('test any directly held component objects', assert => { 58 | const Demeter = require('./LawOfDemeter').Demeter; 59 | const soldier = { 60 | name: 'Rambo', 61 | rank: 'Sergeant', 62 | specialty: ['guns', 'explosives', 'hand-to-hand combat', 'tanks', 'helicopters', 'tactics', 'being a badass'], 63 | years: 5, 64 | job: 'programmer' 65 | }; 66 | const demeter = new Demeter(soldier); 67 | const actual = demeter.civilianPlan.printPlan(); 68 | const expected = 'Civilian Job Plan: programmer'; 69 | assert.equal(actual, expected); 70 | assert.end(); 71 | }); 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pragmatic-programmer-and-clean-code", 3 | "version": "0.1.0", 4 | "description": "Principles from Pragmatic Programmer and Clean Code", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix", 9 | "dry:test": "ava --verbose dry/program.test.js", 10 | "dry:test:solution": "ava --verbose dry/solution/program.test.js", 11 | "dry:test:solution:debug": "npm run ava:debug dry/solution/program.test.js", 12 | "ava:debug": "node --inspect node_modules/ava/profile.js", 13 | "solid:srp:test": "ava --verbose solid/srp/program.test.js", 14 | "postsolid:srp:test": "rimraf solid/srp/soldiers.json", 15 | "solid:srp:test:solution": "tape solid/solution/srp/program.test.js", 16 | "solid:ocp:test:solution": "tape solid/ocp/program.test.js", 17 | "solid:lsp:test:solution1": "ava --verbose solid/lsp/program.test.js", 18 | "solid:lsp:test:solution2": "ava --verbose solid/solution/lsp/program.test.js", 19 | "solid:isp:test": "tape solid/isp/program.test.js", 20 | "solid:dip:test": "ava --verbose solid/dip/Repository.test.js", 21 | "lod:test": "tape lod/LawOfDemeter.test.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/jbelmont/pragmatic-programmer-and-clean-code.git" 26 | }, 27 | "keywords": [ 28 | "Clean", 29 | "Code", 30 | "Solid", 31 | "DRY", 32 | "YAGNI", 33 | "Pragmatic", 34 | "Programmer" 35 | ], 36 | "author": "Jean-Marcel Belmont", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/jbelmont/pragmatic-programmer-and-clean-code/issues" 40 | }, 41 | "homepage": "https://github.com/jbelmont/pragmatic-programmer-and-clean-code#readme", 42 | "dependencies": { 43 | "cross-env": "^3.1.3", 44 | "dotenv": "^2.0.0", 45 | "nano": "^6.2.0", 46 | "winston": "^2.3.0" 47 | }, 48 | "devDependencies": { 49 | "ava": "^0.17.0", 50 | "babel-cli": "^6.18.0", 51 | "babel-core": "^6.21.0", 52 | "babel-eslint": "^7.1.1", 53 | "babel-loader": "^6.2.10", 54 | "babel-plugin-add-module-exports": "^0.2.1", 55 | "babel-plugin-transform-runtime": "^6.15.0", 56 | "babel-preset-es2015": "^6.18.0", 57 | "babel-preset-react": "^6.16.0", 58 | "babel-preset-react-hmre": "^1.1.1", 59 | "babel-preset-stage-0": "^6.16.0", 60 | "babel-register": "^6.18.0", 61 | "eslint": "^3.12.2", 62 | "eslint-config-standard": "^6.2.1", 63 | "eslint-plugin-import": "^2.2.0", 64 | "eslint-plugin-promise": "^3.4.0", 65 | "eslint-plugin-standard": "^2.0.1", 66 | "gitbook-cli": "^2.3.0", 67 | "nock": "^9.0.2", 68 | "nyc": "^10.0.0", 69 | "proxyquire": "^1.7.10", 70 | "rimraf": "^2.5.4", 71 | "shelljs": "^0.7.5", 72 | "sinon": "^2.0.0-pre.5", 73 | "supertest": "^2.0.1", 74 | "tap-nyan": "^1.1.0", 75 | "tape": "^4.6.3", 76 | "testdouble": "^1.10.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /dry/solution/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | 3 | test('Test ugly average function', t => { 4 | const {uglyAverage} = require('./program'); 5 | t.truthy(uglyAverage, 'uglyAverage does exist'); 6 | 7 | // calling uglyAverage returns hard-coded value 8 | const actual = uglyAverage(); 9 | const expected = 3; 10 | t.is(actual, expected, `uglyAverage returns hard-coded value of ${expected}`); 11 | }); 12 | 13 | test('Test refactored average function', t => { 14 | const { 15 | average 16 | } = require('./program'); 17 | 18 | t.truthy(average, 'average does exist'); 19 | 20 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 21 | const actual = average(numbers); 22 | const expected = 5.5; 23 | t.is(actual, expected, `average returns ${expected}`); 24 | }); 25 | 26 | test('Test refactored average function with other implementation', t => { 27 | const { 28 | average2 29 | } = require('./program'); 30 | 31 | t.truthy(average2, 'average2 does exist'); 32 | 33 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 34 | const actual = average2(numbers); 35 | const expected = 5.5; 36 | t.is(actual, expected, `average2 returns ${expected}`); 37 | }); 38 | 39 | test('Test ugly standardDeviation', t => { 40 | const {uglyStandardDeviation} = require('./program'); 41 | 42 | t.truthy(uglyStandardDeviation, 'uglyStandardDeviation does exist'); 43 | 44 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 45 | 46 | const actual = uglyStandardDeviation(numbers); 47 | const expected = 2.87; 48 | t.is(actual, expected, `standardDeviation returns ${expected}`); 49 | }); 50 | 51 | test('Test standardDeviation in MathImpl class', t => { 52 | const { 53 | MathImpl 54 | } = require('./program'); 55 | 56 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 57 | const mathImpl = new MathImpl(numbers); 58 | 59 | const actual = mathImpl.standardDeviation(); 60 | const expected = 2.87; 61 | t.is(actual, expected, `standardDeviation returns ${expected}`); 62 | }); 63 | 64 | test('Test standardDeviation in MathImpl2 class', t => { 65 | const { 66 | MathImpl2 67 | } = require('./program'); 68 | 69 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 70 | const mathImplement = new MathImpl2(numbers); 71 | 72 | const actual = mathImplement.standardDeviation(); 73 | const expected = 2.87; 74 | t.is(actual, expected, `standardDeviation returns ${expected}`); 75 | }); 76 | 77 | test('Test standardDeviation in MathImpl3 class', t => { 78 | const { 79 | MathImpl3 80 | } = require('./program'); 81 | 82 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 83 | const mathImplement = new MathImpl3(numbers); 84 | 85 | const actual = mathImplement.standardDeviation(); 86 | const expected = 2.87; 87 | t.is(actual, expected, `standardDeviation returns ${expected}`); 88 | }); 89 | 90 | test('Test standardDeviation function', t => { 91 | const { 92 | standardDeviation 93 | } = require('./program'); 94 | 95 | const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 96 | 97 | const actual = standardDeviation(numbers); 98 | const expected = 2.87; 99 | t.is(actual, expected, `standardDeviation returns ${expected}`); 100 | }); 101 | -------------------------------------------------------------------------------- /solid/solution/srp/program.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const sinon = require('sinon'); 5 | 6 | const program = require('./program'); 7 | 8 | let soldiers, soldiersPayload, readSoldiersStub, formatSoldiersStub, writeSoldiersStub, sandbox; 9 | test('setup', t => { 10 | soldiers = [ 11 | { 12 | name: 'John Rambo', 13 | rank: 'Sergeant First Class', 14 | branch: 'Army' 15 | }, 16 | { 17 | name: 'Chuck Norris', 18 | rank: 'Tech Sergeant', 19 | branch: 'Air Force' 20 | }, 21 | { 22 | name: 'John Doe', 23 | rank: 'Private', 24 | branch: 'Marines' 25 | }, 26 | { 27 | name: 'Luke Cage', 28 | rank: 'Sergeant', 29 | branch: 'Marines' 30 | }, 31 | { 32 | name: 'Guy Dude', 33 | rank: 'Petty Officer', 34 | branch: 'Navy' 35 | } 36 | ]; 37 | 38 | soldiersPayload = [ 39 | 'John Rambo,Army,Sergeant First Class', 40 | 'Chuck Norris,Air Force,Tech Sergeant', 41 | 'John Doe,Marines,Private', 42 | 'Luke Cage,Marines,Sergeant', 43 | 'Guy Dude,Navy,Petty Officer' 44 | ]; 45 | 46 | sandbox = sinon.sandbox.create(); 47 | readSoldiersStub = sandbox.stub(program, 'readSoldiers').returns(Promise.resolve(soldiersPayload)); 48 | formatSoldiersStub = sandbox.stub(program, 'formatSoldiers'); 49 | formatSoldiersStub.withArgs().throws('TypeError'); 50 | formatSoldiersStub.withArgs(soldiersPayload).returns(soldiers); 51 | writeSoldiersStub = sandbox.stub(program, 'writeSoldiers'); 52 | writeSoldiersStub.withArgs().throws({ 53 | err: { 54 | message: '400' 55 | } 56 | }); 57 | writeSoldiersStub.withArgs(soldiers).returns(200); 58 | t.end(); 59 | }); 60 | 61 | test('Practice Concepts in single responsibility principle', nest => { 62 | nest.test('readSoldiers should return an array of soldiers', assert => { 63 | readSoldiersStub().then(soldiers => { 64 | const expected = soldiersPayload; 65 | assert.deepEqual(soldiers, expected, 'should return an array of soldiers'); 66 | assert.end(); 67 | }); 68 | }); 69 | 70 | nest.test('calling formatSoldiers with no args should return an error', assert => { 71 | try { 72 | formatSoldiersStub(); 73 | } catch(e) { 74 | sinon.assert.threw(formatSoldiersStub, 'TypeError'); 75 | assert.end(); 76 | } 77 | }); 78 | 79 | nest.test('calling formatSoldiers with 1 arg should return a JSON object', assert => { 80 | const formatSoldiers = formatSoldiersStub(soldiersPayload); 81 | const expected = soldiers; 82 | assert.deepEqual(formatSoldiers, expected, 'should return a json object with soldiers'); 83 | assert.end(); 84 | }); 85 | 86 | nest.test('calling createFile with no args should return an error', assert => { 87 | try { 88 | writeSoldiersStub(); 89 | } catch (e) { 90 | sinon.assert.threw(writeSoldiersStub); 91 | } 92 | assert.end(); 93 | }); 94 | 95 | nest.test('calling createFile with 1 arg should return no error and a payload', assert => { 96 | writeSoldiersStub(soldiers); 97 | sinon.assert.calledWith(writeSoldiersStub, soldiers); 98 | assert.end(); 99 | }); 100 | 101 | }); 102 | 103 | test('teardown', t => { 104 | sandbox.restore(); 105 | t.end(); 106 | }); 107 | -------------------------------------------------------------------------------- /solid/srp/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | const {join} = require('path'); 3 | const shell = require('shelljs'); 4 | 5 | test.before(assert => { 6 | const result = shell.touch(join(__dirname, 'soldiers.json')); 7 | assert.is(result.code, 0); 8 | }); 9 | 10 | test('test readSoldiersFormatSoldiersAndWriteSoldiers', assert => { 11 | const {readSoldiersFormatSoldiersAndWriteSoldiers} = require('./program'); 12 | assert.truthy(readSoldiersFormatSoldiersAndWriteSoldiers, 'readSoldiersFormatSoldiersAndWriteSoldiers does exist'); 13 | 14 | try { 15 | const action = readSoldiersFormatSoldiersAndWriteSoldiers(); 16 | assert.pass(action); 17 | } catch(e) { 18 | assert.fail(e); 19 | } 20 | }); 21 | 22 | test('check if readSoldier returns a payload', assert => { 23 | const {readSoldiers} = require('./program.solution'); 24 | const expected = [ 25 | 'John Rambo,Army,Sergeant First Class', 26 | 'Chuck Norris,Air Force,Tech Sergeant', 27 | 'John Doe,Marines,Private', 28 | 'Luke Cage,Marines,Sergeant', 29 | 'Guy Dude,Navy,Petty Officer' 30 | ]; 31 | readSoldiers(function(err, data) { 32 | if (err) { 33 | assert.falsy(err); 34 | } 35 | const soldiers = data; 36 | assert.is(soldiers, expected, 'should return a list of soldiers'); 37 | }); 38 | }); 39 | 40 | test('test formatSoldiers', assert => { 41 | const {formatSoldiers} = require('./program.solution'); 42 | const soldiers = [ 43 | 'John Rambo,Army,Sergeant First Class', 44 | 'Chuck Norris,Air Force,Tech Sergeant', 45 | 'John Doe,Marines,Private', 46 | 'Luke Cage,Marines,Sergeant', 47 | 'Guy Dude,Navy,Petty Officer' 48 | ]; 49 | const expected = [ 50 | { 51 | name: 'John Rambo', 52 | rank: 'Sergeant First Class', 53 | branch: 'Army' 54 | }, 55 | { 56 | name: 'Chuck Norris', 57 | rank: 'Tech Sergeant', 58 | branch: 'Air Force' 59 | }, 60 | { 61 | name: 'John Doe', 62 | rank: 'Private', 63 | branch: 'Marines' 64 | }, 65 | { 66 | name: 'Luke Cage', 67 | rank: 'Sergeant', 68 | branch: 'Marines' 69 | }, 70 | { 71 | name: 'Guy Dude', 72 | rank: 'Petty Officer', 73 | branch: 'Navy' 74 | } 75 | ]; 76 | const actual = formatSoldiers(soldiers); 77 | assert.deepEqual(actual, expected, 'should return a nicely formatted json object'); 78 | }); 79 | 80 | test('test writeSoldiers', assert => { 81 | const {writeSoldiers} = require('./program.solution'); 82 | const soldiers = [ 83 | { 84 | name: 'John Rambo', 85 | rank: 'Sergeant First Class', 86 | branch: 'Army' 87 | }, 88 | { 89 | name: 'Chuck Norris', 90 | rank: 'Tech Sergeant', 91 | branch: 'Air Force' 92 | }, 93 | { 94 | name: 'John Doe', 95 | rank: 'Private', 96 | branch: 'Marines' 97 | }, 98 | { 99 | name: 'Luke Cage', 100 | rank: 'Sergeant', 101 | branch: 'Marines' 102 | }, 103 | { 104 | name: 'Guy Dude', 105 | rank: 'Petty Officer', 106 | branch: 'Navy' 107 | } 108 | ]; 109 | const expected = 'created'; 110 | writeSoldiers(soldiers, ((err, data) => { 111 | if (err) { 112 | assert.falsy(err); 113 | } 114 | const status = data.status; 115 | assert.is(status, expected, 'should return a status of created'); 116 | })); 117 | }); 118 | 119 | test('test removeSoldiers', assert => { 120 | const {removeSoldiers} = require('./program.solution'); 121 | const filePath = `${__dirname}/soldiers.json`; 122 | removeSoldiers(filePath, (err) => { 123 | if (err) { 124 | assert.falsy(err); 125 | } 126 | assert.pass('Removed soldier.json'); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /dry/solution/program.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function uglyAverage() { 4 | var numbers = [1, 2, 3, 4, 5]; 5 | 6 | var sum = 0, i; 7 | for (i = 0; i < numbers.length; i++) { 8 | sum += numbers[i]; 9 | } 10 | return sum / numbers.length; 11 | } 12 | 13 | function uglyStandardDeviation(numbers) { 14 | let sum = 0; 15 | for (let i = 0; i < numbers.length; i++) { 16 | sum += numbers[i]; 17 | } 18 | const avg = sum / numbers.length; 19 | 20 | let summation = 0; 21 | for (let i = 0; i < numbers.length; i++) { 22 | summation += Math.pow(Math.abs(numbers[i] - avg), 2); 23 | } 24 | return Number(Math.sqrt(summation / numbers.length).toFixed(2)); 25 | } 26 | 27 | function average(input) { 28 | return input.reduce((previous, current) => previous + current, 0) / input.length; 29 | } 30 | 31 | function average2(input) { 32 | let sum = 0; 33 | for (let i = 0; i < input.length; i++) { 34 | sum += input[i]; 35 | } 36 | return sum / input.length; 37 | } 38 | 39 | class MathImpl { 40 | 41 | constructor(numbers) { 42 | this.numbers = numbers; 43 | } 44 | 45 | average(numbers) { 46 | return numbers.reduce((previous, current) => previous + current, 0) / this.numbers.length; 47 | } 48 | 49 | printWithTwoCharacters(standardDeviation) { 50 | return Number(standardDeviation.toFixed(2)); 51 | } 52 | 53 | standardDeviation() { 54 | const avg = this.average(this.numbers); 55 | const summation = this.numbers 56 | .map(val => Math.pow(Math.abs(val - avg), 2)) 57 | .reduce((prev, curr) => prev + curr, 0) / this.numbers.length; 58 | return this.printWithTwoCharacters(Math.sqrt(summation)); 59 | } 60 | } 61 | 62 | function MathImpl2(numbers) { 63 | this.numbers = numbers; 64 | 65 | this.average = () => { 66 | return this.numbers.reduce((previous, current) => previous + current, 0) / this.numbers.length; 67 | }; 68 | 69 | this.standardDeviation = () => { 70 | const avg = this.average(this.numbers); 71 | const summation = this.numbers 72 | .map(val => Math.pow(Math.abs(val - avg), 2)) 73 | .reduce((prev, curr) => prev + curr, 0) / this.numbers.length; 74 | return this.printWithTwoCharacters(Math.sqrt(summation)); 75 | }; 76 | 77 | this.printWithTwoCharacters = standardDeviation => { 78 | return Number(standardDeviation.toFixed(2)); 79 | }; 80 | } 81 | 82 | function MathImpl3(numbers) { 83 | this.numbers = numbers; 84 | } 85 | 86 | MathImpl3.prototype.average = function() { 87 | return this.numbers.reduce((previous, current) => previous + current, 0) / this.numbers.length; 88 | }; 89 | 90 | MathImpl3.prototype.printWithTwoCharacters = function(standardDeviation) { 91 | return Number(standardDeviation.toFixed(2)); 92 | }; 93 | 94 | MathImpl3.prototype.standardDeviation = function() { 95 | const avg = MathImpl3.prototype.average.call(this); 96 | const summation = this.numbers 97 | .map(val => Math.pow(Math.abs(val - avg), 2)) 98 | .reduce((prev, curr) => prev + curr, 0) / this.numbers.length; 99 | return MathImpl3.prototype.printWithTwoCharacters.call(null, Math.sqrt(summation)); 100 | }; 101 | 102 | const average3 = input => { 103 | return input.reduce((prev, curr) => prev + curr, 0) / input.length; 104 | }; 105 | 106 | const formatNumbers = standardDeviation => { 107 | return Number(standardDeviation.toFixed(2)); 108 | }; 109 | 110 | const standardDeviation = input => { 111 | const avg = average3(input); 112 | const summation = input 113 | .map(val => Math.pow(Math.abs(val - avg), 2)) 114 | .reduce((prev, curr) => prev + curr, 0) / input.length; 115 | return formatNumbers(Math.sqrt(summation)); 116 | }; 117 | 118 | module.exports = { 119 | uglyAverage, 120 | uglyStandardDeviation, 121 | average, 122 | average2, 123 | MathImpl, 124 | MathImpl2, 125 | MathImpl3, 126 | standardDeviation 127 | }; 128 | -------------------------------------------------------------------------------- /solid/ocp/program.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | 3 | test('setup', assert => { 4 | assert.end(); 5 | }); 6 | 7 | test('test existence of shoppingCart function', assert => { 8 | const {Registry} = require('./Registry'); 9 | assert.ok(Registry); 10 | assert.end(); 11 | }); 12 | 13 | test('test creation of shopping cart', assert => { 14 | const {Registry} = require('./Registry'); 15 | const {Cart} = require('./Cart'); 16 | const expected = [ 17 | { 18 | item1: 'laptop' 19 | }, 20 | { 21 | item2: 'mouse' 22 | }, 23 | { 24 | item3: 'videoGame' 25 | } 26 | ]; 27 | const cart = new Cart(Registry, expected); 28 | const actual = cart._values.showCart().cart; 29 | assert.deepEqual(actual, expected, 'showCart should return list of cart items'); 30 | assert.end(); 31 | }); 32 | 33 | test('test adding item to cart', assert => { 34 | const {Registry} = require('./Registry'); 35 | const {Cart} = require('./Cart'); 36 | const cartItems = [ 37 | { 38 | item1: 'laptop' 39 | }, 40 | { 41 | item2: 'mouse' 42 | }, 43 | { 44 | item3: 'videoGame' 45 | } 46 | ]; 47 | const cart = new Cart(Registry, cartItems); 48 | cart._values.addCart({item4: 'hdmiCable'}); 49 | const actual = cart._values.showCart().cart; 50 | const expected = [ 51 | { 52 | item1: 'laptop' 53 | }, 54 | { 55 | item2: 'mouse' 56 | }, 57 | { 58 | item3: 'videoGame' 59 | }, 60 | { 61 | item4: 'hdmiCable' 62 | } 63 | ]; 64 | assert.deepEqual(actual, expected, 'showCart should return list of cart items'); 65 | assert.end(); 66 | }); 67 | 68 | test('test CreateStore getPreloadedState function', assert => { 69 | const {CreateStore} = require('./Registry'); 70 | const expected = [ 71 | { 72 | item1: 'laptop' 73 | }, 74 | { 75 | item2: 'mouse' 76 | }, 77 | { 78 | item3: 'videoGame' 79 | } 80 | ]; 81 | const ShoppingCart = new CreateStore(null, expected); 82 | const actual = ShoppingCart.getPreloadedState(); 83 | assert.deepEqual(actual, expected, 'getPreloadedState should return items'); 84 | assert.end(); 85 | }); 86 | 87 | test('test Cart2 register function', assert => { 88 | const {CreateStore} = require('./Registry'); 89 | const {Cart2} = require('./Cart'); 90 | const {ADD_CART} = require('./constants'); 91 | const items = [ 92 | { 93 | item1: 'laptop' 94 | }, 95 | { 96 | item2: 'mouse' 97 | }, 98 | { 99 | item3: 'videoGame' 100 | } 101 | ]; 102 | const ShoppingCart = new CreateStore(null, items); 103 | ShoppingCart.register(new Cart2(), items); 104 | const actual = ShoppingCart.getNewState(items, { type: ADD_CART, items: {item4: 'donuts'} }); 105 | const expected = [ 106 | { 107 | item1: 'laptop' 108 | }, 109 | { 110 | item2: 'mouse' 111 | }, 112 | { 113 | item3: 'videoGame' 114 | }, 115 | { 116 | item4: 'donuts' 117 | } 118 | ]; 119 | assert.deepEqual(actual, expected, 'getNewState should return newer cart items'); 120 | assert.end(); 121 | }); 122 | 123 | test('test Cart3 composition strategy', assert => { 124 | const {Cart3, CreateStore2} = require('./Cart'); 125 | const items = [ 126 | { 127 | item1: 'laptop' 128 | }, 129 | { 130 | item2: 'mouse' 131 | }, 132 | { 133 | item3: 'videoGame' 134 | } 135 | ]; 136 | const Cart = Cart3(items); 137 | const store = CreateStore2(Cart, { item4: 'hamburgers' })(); 138 | const expect = [ 139 | { 140 | item1: 'laptop' 141 | }, 142 | { 143 | item2: 'mouse' 144 | }, 145 | { 146 | item3: 'videoGame' 147 | }, 148 | { 149 | item4: 'hamburgers' 150 | } 151 | ]; 152 | assert.deepEqual(store, expect, 'CreateStore2 should return newer state'); 153 | assert.end(); 154 | }); 155 | 156 | test('teardown', assert => { 157 | assert.end(); 158 | }); 159 | -------------------------------------------------------------------------------- /solid/dip/UserModels.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | users: [ 3 | {'id':1,'first_name':'Timothy','last_name':'Cox','email':'tcox0@dion.ne.jp','gender':'Male'}, 4 | {'id':2,'first_name':'Sean','last_name':'Medina','email':'smedina1@addthis.com','gender':'Male'}, 5 | {'id':3,'first_name':'Jonathan','last_name':'Tucker','email':'jtucker2@tripadvisor.com','gender':'Male'}, 6 | {'id':4,'first_name':'Donna','last_name':'Payne','email':'dpayne3@cdbaby.com','gender':'Female'}, 7 | {'id':5,'first_name':'Emily','last_name':'Elliott','email':'eelliott4@pen.io','gender':'Female'}, 8 | {'id':6,'first_name':'Howard','last_name':'Wallace','email':'hwallace5@latimes.com','gender':'Male'}, 9 | {'id':7,'first_name':'Jacqueline','last_name':'George','email':'jgeorge6@soup.io','gender':'Female'}, 10 | {'id':8,'first_name':'Heather','last_name':'Kelly','email':'hkelly7@hubpages.com','gender':'Female'}, 11 | {'id':9,'first_name':'Kenneth','last_name':'Fisher','email':'kfisher8@wunderground.com','gender':'Male'}, 12 | {'id':10,'first_name':'Joan','last_name':'Flores','email':'jflores9@icq.com','gender':'Female'}, 13 | {'id':11,'first_name':'Wanda','last_name':'Hunt','email':'whunta@linkedin.com','gender':'Female'}, 14 | {'id':12,'first_name':'Alice','last_name':'Gordon','email':'agordonb@mozilla.com','gender':'Female'}, 15 | {'id':13,'first_name':'Nicholas','last_name':'Harrison','email':'nharrisonc@fastcompany.com','gender':'Male'}, 16 | {'id':14,'first_name':'Timothy','last_name':'Hudson','email':'thudsond@bloomberg.com','gender':'Male'}, 17 | {'id':15,'first_name':'Nancy','last_name':'Lynch','email':'nlynche@163.com','gender':'Female'}, 18 | {'id':16,'first_name':'Ryan','last_name':'Stevens','email':'rstevensf@dedecms.com','gender':'Male'}, 19 | {'id':17,'first_name':'Shawn','last_name':'Little','email':'slittleg@cnet.com','gender':'Male'}, 20 | {'id':18,'first_name':'Frances','last_name':'Garrett','email':'fgarretth@cargocollective.com','gender':'Female'}, 21 | {'id':19,'first_name':'Brian','last_name':'Nelson','email':'bnelsoni@eepurl.com','gender':'Male'}, 22 | {'id':20,'first_name':'Harry','last_name':'Anderson','email':'handersonj@about.com','gender':'Male'}, 23 | {'id':21,'first_name':'Michelle','last_name':'Nelson','email':'mnelsonk@tiny.cc','gender':'Female'}, 24 | {'id':22,'first_name':'Scott','last_name':'Palmer','email':'spalmerl@canalblog.com','gender':'Male'}, 25 | {'id':23,'first_name':'Helen','last_name':'Day','email':'hdaym@geocities.com','gender':'Female'}, 26 | {'id':24,'first_name':'Aaron','last_name':'Torres','email':'atorresn@rediff.com','gender':'Male'}, 27 | {'id':25,'first_name':'Cheryl','last_name':'Morris','email':'cmorriso@theglobeandmail.com','gender':'Female'}, 28 | {'id':26,'first_name':'Heather','last_name':'Sims','email':'hsimsp@about.me','gender':'Female'}, 29 | {'id':27,'first_name':'Andrew','last_name':'Morales','email':'amoralesq@cbsnews.com','gender':'Male'}, 30 | {'id':28,'first_name':'Kevin','last_name':'Lane','email':'klaner@epa.gov','gender':'Male'}, 31 | {'id':29,'first_name':'Karen','last_name':'Perkins','email':'kperkinss@geocities.com','gender':'Female'}, 32 | {'id':30,'first_name':'Jane','last_name':'Jackson','email':'jjacksont@icq.com','gender':'Female'}, 33 | {'id':31,'first_name':'Roy','last_name':'Green','email':'rgreenu@csmonitor.com','gender':'Male'}, 34 | {'id':32,'first_name':'Louis','last_name':'Berry','email':'lberryv@so-net.ne.jp','gender':'Male'}, 35 | {'id':33,'first_name':'Donald','last_name':'Kennedy','email':'dkennedyw@umich.edu','gender':'Male'}, 36 | {'id':34,'first_name':'Edward','last_name':'Schmidt','email':'eschmidtx@seattletimes.com','gender':'Male'}, 37 | {'id':35,'first_name':'Brenda','last_name':'Bennett','email':'bbennetty@cargocollective.com','gender':'Female'}, 38 | {'id':36,'first_name':'Bonnie','last_name':'Carr','email':'bcarrz@desdev.cn','gender':'Female'}, 39 | {'id':37,'first_name':'Tammy','last_name':'Bailey','email':'tbailey10@technorati.com','gender':'Female'}, 40 | {'id':38,'first_name':'Peter','last_name':'Murray','email':'pmurray11@mozilla.com','gender':'Male'}, 41 | {'id':39,'first_name':'Kathryn','last_name':'Peterson','email':'kpeterson12@bandcamp.com','gender':'Female'}, 42 | {'id':40,'first_name':'Linda','last_name':'Carter','email':'lcarter13@redcross.org','gender':'Female'}, 43 | {'id':41,'first_name':'Scott','last_name':'Howell','email':'showell14@sogou.com','gender':'Male'}, 44 | {'id':42,'first_name':'Lillian','last_name':'Nichols','email':'lnichols15@a8.net','gender':'Female'}, 45 | {'id':43,'first_name':'Frank','last_name':'Wells','email':'fwells16@google.es','gender':'Male'}, 46 | {'id':44,'first_name':'Jean','last_name':'Wheeler','email':'jwheeler17@mlb.com','gender':'Female'}, 47 | {'id':45,'first_name':'Phyllis','last_name':'Arnold','email':'parnold18@deliciousdays.com','gender':'Female'}, 48 | {'id':46,'first_name':'Irene','last_name':'Mills','email':'imills19@nhs.uk','gender':'Female'}, 49 | {'id':47,'first_name':'Rose','last_name':'Anderson','email':'randerson1a@google.pl','gender':'Female'}, 50 | {'id':48,'first_name':'Harry','last_name':'Little','email':'hlittle1b@google.fr','gender':'Male'}, 51 | {'id':49,'first_name':'Ruby','last_name':'Rogers','email':'rrogers1c@digg.com','gender':'Female'}, 52 | {'id':50,'first_name':'Jonathan','last_name':'Carpenter','email':'jcarpenter1d@furl.net','gender':'Male'} 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /data/users.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | usersModel: [ 3 | {'id':1,'first_name':'Timothy','last_name':'Cox','email':'tcox0@dion.ne.jp','gender':'Male'}, 4 | {'id':2,'first_name':'Sean','last_name':'Medina','email':'smedina1@addthis.com','gender':'Male'}, 5 | {'id':3,'first_name':'Jonathan','last_name':'Tucker','email':'jtucker2@tripadvisor.com','gender':'Male'}, 6 | {'id':4,'first_name':'Donna','last_name':'Payne','email':'dpayne3@cdbaby.com','gender':'Female'}, 7 | {'id':5,'first_name':'Emily','last_name':'Elliott','email':'eelliott4@pen.io','gender':'Female'}, 8 | {'id':6,'first_name':'Howard','last_name':'Wallace','email':'hwallace5@latimes.com','gender':'Male'}, 9 | {'id':7,'first_name':'Jacqueline','last_name':'George','email':'jgeorge6@soup.io','gender':'Female'}, 10 | {'id':8,'first_name':'Heather','last_name':'Kelly','email':'hkelly7@hubpages.com','gender':'Female'}, 11 | {'id':9,'first_name':'Kenneth','last_name':'Fisher','email':'kfisher8@wunderground.com','gender':'Male'}, 12 | {'id':10,'first_name':'Joan','last_name':'Flores','email':'jflores9@icq.com','gender':'Female'}, 13 | {'id':11,'first_name':'Wanda','last_name':'Hunt','email':'whunta@linkedin.com','gender':'Female'}, 14 | {'id':12,'first_name':'Alice','last_name':'Gordon','email':'agordonb@mozilla.com','gender':'Female'}, 15 | {'id':13,'first_name':'Nicholas','last_name':'Harrison','email':'nharrisonc@fastcompany.com','gender':'Male'}, 16 | {'id':14,'first_name':'Timothy','last_name':'Hudson','email':'thudsond@bloomberg.com','gender':'Male'}, 17 | {'id':15,'first_name':'Nancy','last_name':'Lynch','email':'nlynche@163.com','gender':'Female'}, 18 | {'id':16,'first_name':'Ryan','last_name':'Stevens','email':'rstevensf@dedecms.com','gender':'Male'}, 19 | {'id':17,'first_name':'Shawn','last_name':'Little','email':'slittleg@cnet.com','gender':'Male'}, 20 | {'id':18,'first_name':'Frances','last_name':'Garrett','email':'fgarretth@cargocollective.com','gender':'Female'}, 21 | {'id':19,'first_name':'Brian','last_name':'Nelson','email':'bnelsoni@eepurl.com','gender':'Male'}, 22 | {'id':20,'first_name':'Harry','last_name':'Anderson','email':'handersonj@about.com','gender':'Male'}, 23 | {'id':21,'first_name':'Michelle','last_name':'Nelson','email':'mnelsonk@tiny.cc','gender':'Female'}, 24 | {'id':22,'first_name':'Scott','last_name':'Palmer','email':'spalmerl@canalblog.com','gender':'Male'}, 25 | {'id':23,'first_name':'Helen','last_name':'Day','email':'hdaym@geocities.com','gender':'Female'}, 26 | {'id':24,'first_name':'Aaron','last_name':'Torres','email':'atorresn@rediff.com','gender':'Male'}, 27 | {'id':25,'first_name':'Cheryl','last_name':'Morris','email':'cmorriso@theglobeandmail.com','gender':'Female'}, 28 | {'id':26,'first_name':'Heather','last_name':'Sims','email':'hsimsp@about.me','gender':'Female'}, 29 | {'id':27,'first_name':'Andrew','last_name':'Morales','email':'amoralesq@cbsnews.com','gender':'Male'}, 30 | {'id':28,'first_name':'Kevin','last_name':'Lane','email':'klaner@epa.gov','gender':'Male'}, 31 | {'id':29,'first_name':'Karen','last_name':'Perkins','email':'kperkinss@geocities.com','gender':'Female'}, 32 | {'id':30,'first_name':'Jane','last_name':'Jackson','email':'jjacksont@icq.com','gender':'Female'}, 33 | {'id':31,'first_name':'Roy','last_name':'Green','email':'rgreenu@csmonitor.com','gender':'Male'}, 34 | {'id':32,'first_name':'Louis','last_name':'Berry','email':'lberryv@so-net.ne.jp','gender':'Male'}, 35 | {'id':33,'first_name':'Donald','last_name':'Kennedy','email':'dkennedyw@umich.edu','gender':'Male'}, 36 | {'id':34,'first_name':'Edward','last_name':'Schmidt','email':'eschmidtx@seattletimes.com','gender':'Male'}, 37 | {'id':35,'first_name':'Brenda','last_name':'Bennett','email':'bbennetty@cargocollective.com','gender':'Female'}, 38 | {'id':36,'first_name':'Bonnie','last_name':'Carr','email':'bcarrz@desdev.cn','gender':'Female'}, 39 | {'id':37,'first_name':'Tammy','last_name':'Bailey','email':'tbailey10@technorati.com','gender':'Female'}, 40 | {'id':38,'first_name':'Peter','last_name':'Murray','email':'pmurray11@mozilla.com','gender':'Male'}, 41 | {'id':39,'first_name':'Kathryn','last_name':'Peterson','email':'kpeterson12@bandcamp.com','gender':'Female'}, 42 | {'id':40,'first_name':'Linda','last_name':'Carter','email':'lcarter13@redcross.org','gender':'Female'}, 43 | {'id':41,'first_name':'Scott','last_name':'Howell','email':'showell14@sogou.com','gender':'Male'}, 44 | {'id':42,'first_name':'Lillian','last_name':'Nichols','email':'lnichols15@a8.net','gender':'Female'}, 45 | {'id':43,'first_name':'Frank','last_name':'Wells','email':'fwells16@google.es','gender':'Male'}, 46 | {'id':44,'first_name':'Jean','last_name':'Wheeler','email':'jwheeler17@mlb.com','gender':'Female'}, 47 | {'id':45,'first_name':'Phyllis','last_name':'Arnold','email':'parnold18@deliciousdays.com','gender':'Female'}, 48 | {'id':46,'first_name':'Irene','last_name':'Mills','email':'imills19@nhs.uk','gender':'Female'}, 49 | {'id':47,'first_name':'Rose','last_name':'Anderson','email':'randerson1a@google.pl','gender':'Female'}, 50 | {'id':48,'first_name':'Harry','last_name':'Little','email':'hlittle1b@google.fr','gender':'Male'}, 51 | {'id':49,'first_name':'Ruby','last_name':'Rogers','email':'rrogers1c@digg.com','gender':'Female'}, 52 | {'id':50,'first_name':'Jonathan','last_name':'Carpenter','email':'jcarpenter1d@furl.net','gender':'Male'} 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /solid/dip/Repository.test.js: -------------------------------------------------------------------------------- 1 | const test = require('ava'); 2 | const sinon = require('sinon'); 3 | const {Repository} = require('./Repository'); 4 | 5 | let insertStub, insertDoc, insertResult, retrieveStub, retrieveDoc, retrieveResult, retrieveErrorResult; 6 | let deleteStub, deleteErrorResult, deleteDoc, updateStub, updateDoc, updateResult, updateErrorResult, sandbox; 7 | test.before(t => { 8 | sandbox = sinon.sandbox.create(); 9 | const Repo = new Repository(); 10 | insertStub = sandbox.stub(Repo, 'insert'); 11 | insertStub.withArgs().returns(Promise.reject({ 12 | 'err': { 13 | 'statusCode': '400' 14 | }, 15 | 'message': 'Missing name/document.' 16 | })); 17 | insertDoc = { 18 | dbName: 'pragmaticprogrammer', 19 | name: 'example1', 20 | body: { 21 | hero1: 'hero1', 22 | hero2: 'hero2' 23 | } 24 | }; 25 | insertResult = { 26 | _id: 'example1', 27 | _rev: '5-933532f44c219640a9cd38a813316ac0', 28 | hero1: 'hero1', 29 | hero2: 'hero2' 30 | }; 31 | insertStub.withArgs(insertDoc.dbName, insertDoc.name, insertDoc.body).returns(Promise.resolve(insertResult)); 32 | retrieveStub = sandbox.stub(Repo, 'retrieveDocument'); 33 | retrieveDoc = { 34 | dbName: 'pragmaticprogrammer', 35 | name: 'example1' 36 | }; 37 | retrieveResult = { 38 | _id: 'example1', 39 | _rev: '7-04aeae92f29821ac73c52b5569f5064e', 40 | hero1: 'hero1', 41 | hero2: 'hero2' 42 | }; 43 | retrieveErrorResult = { 44 | error: { 45 | message: 'Improper arguments passed' 46 | } 47 | }; 48 | retrieveStub.withArgs().returns(Promise.reject(retrieveErrorResult)); 49 | retrieveStub.withArgs(retrieveDoc.dbName, retrieveDoc.name).returns(Promise.resolve(retrieveResult)); 50 | updateStub = sandbox.stub(Repo, 'update'); 51 | updateDoc = { 52 | dbName: 'pragmaticprogrammer', 53 | name: 'example1', 54 | body: { 55 | songs: ['Everybody Want to Rule the World', 'Thriller', 'No Sleep for Brooklyn'] 56 | } 57 | }; 58 | updateResult = { 59 | body: { 60 | 'songs': ['Everybody Want to Rule the World', 'Thriller', 'No Sleep for Brooklyn'] 61 | } 62 | }; 63 | updateErrorResult = null; 64 | updateStub.withArgs().returns(Promise.reject(updateErrorResult)); 65 | updateStub.withArgs(updateDoc.dbName, updateDoc.name, updateDoc.body).returns(Promise.resolve(updateResult)); 66 | deleteStub = sandbox.stub(Repo, 'delete'); 67 | deleteErrorResult = 'GET /undefined 404'; 68 | deleteDoc = { 69 | dbName: 'pragmaticprogrammer', 70 | name: 'example1' 71 | }; 72 | deleteStub.withArgs().returns(Promise.reject(deleteErrorResult)); 73 | deleteStub.withArgs(deleteDoc.dbName, deleteDoc.name).returns(Promise.resolve(null)); 74 | t.pass(true); 75 | }); 76 | 77 | test.after('cleanup', t => { 78 | sandbox.restore(); 79 | t.pass(true); 80 | }); 81 | 82 | test('test insert method returns error object when given improper arguments', assert => { 83 | insertStub() 84 | .then(result => result) 85 | .catch(error => { 86 | const actual = error['err']['statusCode']; 87 | const expected = '400'; 88 | assert.is(actual, expected, `should return status code of ${expected}`); 89 | }); 90 | }); 91 | 92 | test('test insert method returns an object when given proper arguments', assert => { 93 | insertStub(insertDoc.dbName, insertDoc.name, insertDoc.body).then(actual => { 94 | const expected = insertResult; 95 | assert.is(actual, expected, `should return ${expected}`); 96 | }); 97 | }); 98 | 99 | test('test retrieveDocument method returns error object when given improper arguments', assert => { 100 | retrieveStub() 101 | .then(result => result) 102 | .catch(error => { 103 | const actual = error['error']['message']; 104 | const expected = retrieveErrorResult.error.message; 105 | assert.is(actual, expected, `should return string: ${expected}`); 106 | }); 107 | }); 108 | 109 | test('test retrieveDocument method returns object when given proper arugments', assert => { 110 | retrieveStub(retrieveDoc.dbName, retrieveDoc.name) 111 | .then(result => { 112 | const actual = result['hero1']; 113 | const expected = retrieveResult['hero1']; 114 | assert.is(actual, expected, `should return ${expected}`); 115 | }); 116 | }); 117 | 118 | test('test update method returns error object when given improper arguments', assert => { 119 | updateStub() 120 | .then(result => { 121 | assert.truthy(result); 122 | }) 123 | .catch(err => { 124 | assert.falsy(err); 125 | }); 126 | }); 127 | 128 | test('test update method updates object and returns newer object', assert => { 129 | updateStub(updateDoc.dbName, updateDoc.name, updateDoc.body) 130 | .then(result => { 131 | const actual = result['songs']; 132 | const expected = ['Everybody Want to Rule the World', 'Thriller', 'No Sleep for Brooklyn']; 133 | assert.deepEqual(actual, expected, `should return the following songs: ${expected}`); 134 | }); 135 | }); 136 | 137 | test('test delete method returns error when passed improper arguments', assert => { 138 | deleteStub() 139 | .then(result => assert.truth(result)) 140 | .catch(err => assert.is(deleteErrorResult, err, `should return ${err}`)); 141 | }); 142 | 143 | test('test delete method returns null when passed proper arguments', assert => { 144 | deleteStub(deleteDoc.dbName, deleteDoc.name) 145 | .then(result => assert.falsy(result)) 146 | .catch(err => console.log(err)); 147 | }); 148 | -------------------------------------------------------------------------------- /docs/pragmaticprogrammertips.md: -------------------------------------------------------------------------------- 1 | ### Care About Your Craft 2 | 3 | Why spend your life developing software unless you care about doing it well? 4 | 5 | ### Provide Options, Don’t Make Lame Excuses 6 | 7 | Instead of excuses, provide options. Don’t say it can’t be done; explain what can be done. 8 | 9 | ### Be a Catalyst for Change 10 | 11 | You can’t force change on people. Instead, show them how the future might be and help them participate in creating it. 12 | 13 | ### Make Quality a Requirements Issue 14 | 15 | Involve your users in determining the project’s real quality requirements. 16 | 17 | ### Critically Analyze What You Read and Hear 18 | 19 | Don’t be swayed by vendors, media hype, or dogma. Analyze information in terms of you and your project. 20 | 21 | ### DRY—Don’t Repeat Yourself 22 | 23 | Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. 24 | 25 | ### Eliminate Effects Between Unrelated Things 26 | 27 | Design components that are self-contained, independent, and have a single, well-defined purpose. 28 | 29 | ### Use Tracer Bullets to Find the Target 30 | 31 | Tracer bullets let you home in on your target by trying things and seeing how close they land. 32 | 33 | ### Program Close to the Problem Domain 34 | 35 | Design and code in your user’s language. 36 | 37 | ### Iterate the Schedule with the Code 38 | 39 | Use experience you gain as you implement to refine the project time scales. 40 | 41 | ### Use the Power of Command Shells 42 | 43 | Use the shell when graphical user interfaces don’t cut it. 44 | 45 | ### Always Use Source Code Control 46 | 47 | Source code control is a time machine for your work—you can go back. 48 | 49 | ### Don’t Panic When Debugging 50 | 51 | Take a deep breath and THINK! about what could be causing the bug. 52 | 53 | ### Don’t Assume It—Prove It 54 | 55 | Prove your assumptions in the actual environment—with real data and boundary conditions. 56 | 57 | ### Write Code That Writes Code 58 | 59 | Code generators increase your productivity and help avoid duplication. 60 | 61 | ### Design with Contracts 62 | 63 | Use contracts to document and verify that code does no more and no less than it claims to do. 64 | 65 | ### Use Assertions to Prevent the Impossible 66 | 67 | Assertions validate your assumptions. Use them to protect your code from an uncertain world. 68 | 69 | ### Finish What You Start 70 | 71 | Where possible, the routine or object that allocates a resource should be responsible for deallocating it. 72 | 73 | ### Configure, Don’t Integrate 74 | 75 | Implement technology choices for an application as configuration options, not through integration or engineering. 76 | 77 | ### Analyze Workflow to Improve Concurrency 78 | 79 | Exploit concurrency in your user’s workflow. 80 | 81 | ### Always Design for Concurrency 82 | 83 | Allow for concurrency, and you’ll design cleaner interfaces with fewer assumptions. 84 | 85 | ### Use Blackboards to Coordinate Workflow 86 | 87 | Use blackboards to coordinate disparate facts and agents, while maintaining independence and isolation among participants. 88 | 89 | ### Estimate the Order of Your Algorithms 90 | 91 | Get a feel for how long things are likely to take before you write code. 92 | 93 | ### Refactor Early, Refactor Often 94 | 95 | Just as you might weed and rearrange a garden, rewrite, rework, and re-architect code when it needs it. Fix the root of the problem. 96 | 97 | ### Test Your Software, or Your Users Will 98 | 99 | Test ruthlessly. Don’t make your users find bugs for you. 100 | 101 | ### Don’t Gather Requirements—Dig for Them 102 | 103 | Requirements rarely lie on the surface. They’re buried deep beneath layers of assumptions, misconceptions, and politics. 104 | 105 | ### Abstractions Live Longer than Details 106 | 107 | Invest in the abstraction, not the implementation. Abstractions can survive the barrage of changes from different implementations and new technologies. 108 | 109 | ### Don’t Think Outside the Box—Find the Box 110 | 111 | When faced with an impossible problem, identify the real constraints. Ask yourself: “Does it have to be done this way? Does it have to be done at all?” 112 | 113 | ### Some Things Are Better Done than Described 114 | 115 | Don’t fall into the specification spiral—at some point you need to start coding. 116 | 117 | ### Costly Tools Don’t Produce Better Designs 118 | 119 | Beware of vendor hype, industry dogma, and the aura of the price tag. Judge tools on their merits. 120 | 121 | ### Don’t Use Manual Procedures 122 | 123 | A shell script or batch file will execute the same instructions, in the same order, time after time. 124 | 125 | ### Coding Ain’t Done ‘Til All the Tests Run 126 | 127 | ‘Nuff said. 128 | 129 | ### Test State Coverage, Not Code Coverage 130 | 131 | Identify and test significant program states. Just testing lines of code isn’t enough. 132 | 133 | ### English is Just a Programming Language 134 | 135 | Write documents as you would write code: honor the DRY principle, use metadata, MVC, automatic generation, and so on. 136 | 137 | ### Gently Exceed Your Users’ Expectations 138 | 139 | Come to understand your users’ expectations, then deliver just that little bit more. 140 | 141 | ### Think! About Your Work 142 | 143 | Turn off the autopilot and take control. Constantly critique and appraise your work. 144 | 145 | ### Don’t Live with Broken Windows 146 | 147 | Fix bad designs, wrong decisions, and poor code when you see them. 148 | 149 | ### Remember the Big Picture 150 | 151 | Don’t get so engrossed in the details that you forget to check what’s happening around you. 152 | 153 | ### Invest Regularly in Your Knowledge Portfolio 154 | 155 | Make learning a habit. 156 | 157 | ### It’s Both What You Say and the Way You Say It 158 | 159 | There’s no point in having great ideas if you don’t communicate them effectively. 160 | 161 | ### Make It Easy to Reuse 162 | 163 | If it’s easy to reuse, people will. Create an environment that supports reuse. 164 | 165 | ### There Are No Final Decisions 166 | 167 | No decision is cast in stone. Instead, consider each as being written in the sand at the beach, and plan for change. 168 | 169 | ### Prototype to Learn 170 | 171 | Prototyping is a learning experience. Its value lies not in the code you produce, but in the lessons you learn. 172 | 173 | ### Estimate to Avoid Surprises 174 | 175 | Estimate before you start. You’ll spot potential problems up front. 176 | 177 | ### Keep Knowledge in Plain Text 178 | 179 | Plain text won’t become obsolete. It helps leverage your work and simplifies debugging and testing. 180 | 181 | ### Use a Single Editor Well 182 | 183 | The editor should be an extension of your hand; make sure your editor is configurable, extensible, and programmable. 184 | 185 | ### Fix the Problem, Not the Blame 186 | 187 | It doesn’t really matter whether the bug is your fault or someone else’s—it is still your problem, and it still needs to be fixed. 188 | 189 | ### “select” Isn’t Broken 190 | 191 | It is rare to find a bug in the OS or the compiler, or even a third-party product or library. The bug is most likely in the application. 192 | 193 | ### Learn a Text Manipulation Language 194 | 195 | You spend a large part of each day working with text. Why not have the computer do some of it for you? 196 | 197 | ### You Can’t Write Perfect Software 198 | 199 | Software can’t be perfect. Protect your code and users from the inevitable errors. 200 | 201 | ### Crash Early 202 | 203 | A dead program normally does a lot less damage than a crippled one. 204 | 205 | ### Use Exceptions for Exceptional Problems 206 | 207 | Exceptions can suffer from all the readability and maintainability problems of classic spaghetti code. Reserve exceptions for exceptional things. 208 | 209 | ### Minimize Coupling Between Modules 210 | 211 | Avoid coupling by writing “shy” code and applying the Law of Demeter. 212 | 213 | ### Put Abstractions in Code, Details in Metadata 214 | 215 | Program for the general case, and put the specifics outside the compiled code base. 216 | 217 | ### Design Using Services 218 | 219 | Design in terms of services—independent, concurrent objects behind well-defined, consistent interfaces. 220 | 221 | ### Separate Views from Models 222 | 223 | Gain flexibility at low cost by designing your application in terms of models and views. 224 | 225 | ### Don’t Program by Coincidence 226 | 227 | Rely only on reliable things. Beware of accidental complexity, and don’t confuse a happy coincidence with a purposeful plan. 228 | 229 | ### Test Your Estimates 230 | 231 | Mathematical analysis of algorithms doesn’t tell you everything. Try timing your code in its target environment. 232 | 233 | ### Design to Test 234 | 235 | Start thinking about testing before you write a line of code. 236 | 237 | ### Don’t Use Wizard Code You Don’t Understand 238 | 239 | Wizards can generate reams of code. Make sure you understand all of it before you incorporate it into your project. 240 | 241 | ### Work with a User to Think Like a User 242 | 243 | It’s the best way to gain insight into how the system will really be used. 244 | 245 | ### Use a Project Glossary 246 | 247 | Create and maintain a single source of all the specific terms and vocabulary for a project. 248 | 249 | ### Start When You’re Ready 250 | 251 | You’ve been building experience all your life. Don’t ignore niggling doubts. 252 | 253 | ### Don’t Be a Slave to Formal Methods 254 | 255 | Don’t blindly adopt any technique without putting it into the context of your development practices and capabilities. 256 | 257 | ### Organize Teams Around Functionality 258 | 259 | Don’t separate designers from coders, testers from data modelers. Build teams the way you build code. 260 | 261 | ### Test Early. Test Often. Test Automatically. 262 | 263 | Tests that run with every build are much more effective than test plans that sit on a shelf. 264 | 265 | ### Use Saboteurs to Test Your Testing 266 | 267 | Introduce bugs on purpose in a separate copy of the source to verify that testing will catch them. 268 | 269 | ### Find Bugs Once 270 | 271 | Once a human tester finds a bug, it should be the last time a human tester finds that bug. Automatic tests should check for it from then on. 272 | 273 | ### Build Documentation In, Don’t Bolt It On 274 | 275 | Documentation created separately from code is less likely to be correct and up to date. 276 | 277 | ### Sign Your Work 278 | 279 | Craftsmen of an earlier age were proud to sign their work. You should be, too. 280 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pragmatic Programmer and Clean Code 2 | 3 | ## Table of Contents 4 | 5 | 1. [Setup](#setup-instructions) 6 | 2. [Assignments](#assignments) 7 | 3. [DRY Assignment](#dry-assignment) 8 | 4. [SOLID](#solid) 9 | 5. [SOLID Exercises](#exercise-instructions-for-solid) 10 | 6. [Law of Demeter](#law-of-demeter) 11 | 7. [Exercise for Law of Demeter](#exercise-for-law-of-demeter) 12 | 8. [YAGNI](docs/yagni.md) 13 | 9. [KISS](#kiss) 14 | 10. [Pragmatic Software Development Tips](#pragmatic-software-development-tips) 15 | 16 | This meetup will go over concepts from the books [Pragmatic Programmer](https://pragprog.com/book/tpp/the-pragmatic-programmer) and [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 17 | 18 | # Setup instructions 19 | 20 | ### 1. Install node.js via [Node.js](https://nodejs.org/en/download/) 21 | 22 | ### 2. Run `npm install` on root level of folder. 23 | 24 | ### 3. Run `npm run` to see all the scripts available 25 | 26 | # Assignments 27 | 28 | * Each assignment folder has several exercise files. 29 | 30 | * Read the assignment.md file in order to complete the exercise. 31 | 32 | * Some assignments may or may not have a program.js file that you will test. 33 | 34 | * Each assignment will have a file called program.test.js. 35 | 36 | ## DRY Assignment 37 | 38 | #### 1. Go to `dry` folder 39 | 40 | #### 2. Open `assignment.md` 41 | 42 | #### 3. Follow instructions to complete the assignment. 43 | 44 | #### 4. Run command `npm run dry:test` 45 | 46 | ## SOLID 47 | 48 | 1. Single Responsibility Principle 49 | 1. a class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class). 50 | 2. * :scroll: [Single Responsibility Principle](docs/solid/srp.pdf) 51 | 2. Open/Closed Principle 52 | 1. "software entities … should be open for extension, but closed for modification."\ 53 | 2. * :scroll: [Open Closed Principle](docs/solid/ocp.pdf) 54 | 3. Liskov Subsitution Principle 55 | 1. "objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program." 56 | 2. * :scroll: [Liskov Substitution Principle](docs/solid/lsp.pdf) 57 | 4. Interface Segregation Principle 58 | 1. "many client-specific interfaces are better than one general-purpose interface." 59 | 2. * :scroll: [Interface Segregation Principle](docs/solid/isp.pdf) 60 | 5. Dependency Inversion Principle 61 | 1. one should "depend upon abstractions, [not] concretions." 62 | 2. * :scroll: [Dependency Inversion Principle](docs/solid/dip.pdf) 63 | 64 | ### Exercise Instructions for SOLID 65 | 66 | 1. Go to srp folder with `cd solid/srp` 67 | 1. Run command `npm run solid:srp:test` 68 | 2. Finish exercises by passing the tests. 69 | 2. Go to ocp folder with `cd solid/ocp` 70 | 1. Run command `npm run solid:ocp:test:solution` 71 | 2. Mob/Pair program Exercise 72 | 3. Go to lsp folder with `cd solid/lsp` 73 | 1. Run command `npm run solid:lsp:test:solution1` 74 | 2. Run command `npm run solid:lsp:test:solution2` 75 | 3. Mob/Pair program Exercise 76 | 4. Go to isp folder with `cd solid/isp` 77 | 1. Run command `npm run solid:isp:test` 78 | 2. Mob/Pair program Exercise 79 | 5. Go to dip folder with `cd solid/dip` 80 | 1. Run command `npm run solid:dip:test` 81 | 2. Mob/Pair program Exercise 82 | 83 | ## Law of Demeter 84 | 85 | The Law of Demeter (LoD) or principle of least knowledge is a design guideline for developing software, particularly object-oriented programs. In its general form, the LoD is a specific case of loose coupling. 86 | 87 | 1. Each unit should have only limited knowledge about other units: only units "closely" related to the current unit. 88 | 2. Each unit should only talk to its friends; don't talk to strangers. 89 | 3. Only talk to your immediate friends. 90 | 91 | * :scroll: [Law of Demeter](docs/demeter.pdf) 92 | 93 | ### Exercise for Law of Demeter 94 | 95 | This will be a group exercise where we mob program. 96 | 97 | ### KISS 98 | 99 | Keep it Simple, Stupid is a simple concept about eliminating complexity. 100 | 101 | ## Pragmatic Software Development Tips 102 | 103 | ### Care About Your Craft 104 | 105 | Why spend your life developing software unless you care about doing it well? 106 | 107 | ### Provide Options, Don’t Make Lame Excuses 108 | 109 | Instead of excuses, provide options. Don’t say it can’t be done; explain what can be done. 110 | 111 | ### Be a Catalyst for Change 112 | 113 | You can’t force change on people. Instead, show them how the future might be and help them participate in creating it. 114 | 115 | ### Make Quality a Requirements Issue 116 | 117 | Involve your users in determining the project’s real quality requirements. 118 | 119 | ### Critically Analyze What You Read and Hear 120 | 121 | Don’t be swayed by vendors, media hype, or dogma. Analyze information in terms of you and your project. 122 | 123 | ### DRY—Don’t Repeat Yourself 124 | 125 | Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. 126 | 127 | ### Eliminate Effects Between Unrelated Things 128 | 129 | Design components that are self-contained, independent, and have a single, well-defined purpose. 130 | 131 | ### Use Tracer Bullets to Find the Target 132 | 133 | Tracer bullets let you home in on your target by trying things and seeing how close they land. 134 | 135 | ### Program Close to the Problem Domain 136 | 137 | Design and code in your user’s language. 138 | 139 | ### Iterate the Schedule with the Code 140 | 141 | Use experience you gain as you implement to refine the project time scales. 142 | 143 | ### Use the Power of Command Shells 144 | 145 | Use the shell when graphical user interfaces don’t cut it. 146 | 147 | ### Always Use Source Code Control 148 | 149 | Source code control is a time machine for your work—you can go back. 150 | 151 | ### Don’t Panic When Debugging 152 | 153 | Take a deep breath and THINK! about what could be causing the bug. 154 | 155 | ### Don’t Assume It—Prove It 156 | 157 | Prove your assumptions in the actual environment—with real data and boundary conditions. 158 | 159 | ### Write Code That Writes Code 160 | 161 | Code generators increase your productivity and help avoid duplication. 162 | 163 | ### Design with Contracts 164 | 165 | Use contracts to document and verify that code does no more and no less than it claims to do. 166 | 167 | ### Use Assertions to Prevent the Impossible 168 | 169 | Assertions validate your assumptions. Use them to protect your code from an uncertain world. 170 | 171 | ### Finish What You Start 172 | 173 | Where possible, the routine or object that allocates a resource should be responsible for deallocating it. 174 | 175 | ### Configure, Don’t Integrate 176 | 177 | Implement technology choices for an application as configuration options, not through integration or engineering. 178 | 179 | ### Analyze Workflow to Improve Concurrency 180 | 181 | Exploit concurrency in your user’s workflow. 182 | 183 | ### Always Design for Concurrency 184 | 185 | Allow for concurrency, and you’ll design cleaner interfaces with fewer assumptions. 186 | 187 | ### Use Blackboards to Coordinate Workflow 188 | 189 | Use blackboards to coordinate disparate facts and agents, while maintaining independence and isolation among participants. 190 | 191 | ### Estimate the Order of Your Algorithms 192 | 193 | Get a feel for how long things are likely to take before you write code. 194 | 195 | ### Refactor Early, Refactor Often 196 | 197 | Just as you might weed and rearrange a garden, rewrite, rework, and re-architect code when it needs it. Fix the root of the problem. 198 | 199 | ### Test Your Software, or Your Users Will 200 | 201 | Test ruthlessly. Don’t make your users find bugs for you. 202 | 203 | ### Don’t Gather Requirements—Dig for Them 204 | 205 | Requirements rarely lie on the surface. They’re buried deep beneath layers of assumptions, misconceptions, and politics. 206 | 207 | ### Abstractions Live Longer than Details 208 | 209 | Invest in the abstraction, not the implementation. Abstractions can survive the barrage of changes from different implementations and new technologies. 210 | 211 | ### Don’t Think Outside the Box—Find the Box 212 | 213 | When faced with an impossible problem, identify the real constraints. Ask yourself: “Does it have to be done this way? Does it have to be done at all?” 214 | 215 | ### Some Things Are Better Done than Described 216 | 217 | Don’t fall into the specification spiral—at some point you need to start coding. 218 | 219 | ### Costly Tools Don’t Produce Better Designs 220 | 221 | Beware of vendor hype, industry dogma, and the aura of the price tag. Judge tools on their merits. 222 | 223 | ### Don’t Use Manual Procedures 224 | 225 | A shell script or batch file will execute the same instructions, in the same order, time after time. 226 | 227 | ### Coding Ain’t Done ‘Til All the Tests Run 228 | 229 | ‘Nuff said. 230 | 231 | ### Test State Coverage, Not Code Coverage 232 | 233 | Identify and test significant program states. Just testing lines of code isn’t enough. 234 | 235 | ### English is Just a Programming Language 236 | 237 | Write documents as you would write code: honor the DRY principle, use metadata, MVC, automatic generation, and so on. 238 | 239 | ### Gently Exceed Your Users’ Expectations 240 | 241 | Come to understand your users’ expectations, then deliver just that little bit more. 242 | 243 | ### Think! About Your Work 244 | 245 | Turn off the autopilot and take control. Constantly critique and appraise your work. 246 | 247 | ### Don’t Live with Broken Windows 248 | 249 | Fix bad designs, wrong decisions, and poor code when you see them. 250 | 251 | ### Remember the Big Picture 252 | 253 | Don’t get so engrossed in the details that you forget to check what’s happening around you. 254 | 255 | ### Invest Regularly in Your Knowledge Portfolio 256 | 257 | Make learning a habit. 258 | 259 | ### It’s Both What You Say and the Way You Say It 260 | 261 | There’s no point in having great ideas if you don’t communicate them effectively. 262 | 263 | ### Make It Easy to Reuse 264 | 265 | If it’s easy to reuse, people will. Create an environment that supports reuse. 266 | 267 | ### There Are No Final Decisions 268 | 269 | No decision is cast in stone. Instead, consider each as being written in the sand at the beach, and plan for change. 270 | 271 | ### Prototype to Learn 272 | 273 | Prototyping is a learning experience. Its value lies not in the code you produce, but in the lessons you learn. 274 | 275 | ### Estimate to Avoid Surprises 276 | 277 | Estimate before you start. You’ll spot potential problems up front. 278 | 279 | ### Keep Knowledge in Plain Text 280 | 281 | Plain text won’t become obsolete. It helps leverage your work and simplifies debugging and testing. 282 | 283 | ### Use a Single Editor Well 284 | 285 | The editor should be an extension of your hand; make sure your editor is configurable, extensible, and programmable. 286 | 287 | ### Fix the Problem, Not the Blame 288 | 289 | It doesn’t really matter whether the bug is your fault or someone else’s—it is still your problem, and it still needs to be fixed. 290 | 291 | ### "select" Isn’t Broken 292 | 293 | It is rare to find a bug in the OS or the compiler, or even a third-party product or library. The bug is most likely in the application. 294 | 295 | ### Learn a Text Manipulation Language 296 | 297 | You spend a large part of each day working with text. Why not have the computer do some of it for you? 298 | 299 | ### You Can’t Write Perfect Software 300 | 301 | Software can’t be perfect. Protect your code and users from the inevitable errors. 302 | 303 | ### Crash Early 304 | 305 | A dead program normally does a lot less damage than a crippled one. 306 | 307 | ### Use Exceptions for Exceptional Problems 308 | 309 | Exceptions can suffer from all the readability and maintainability problems of classic spaghetti code. Reserve exceptions for exceptional things. 310 | 311 | ### Minimize Coupling Between Modules 312 | 313 | Avoid coupling by writing “shy” code and applying the Law of Demeter. 314 | 315 | ### Put Abstractions in Code, Details in Metadata 316 | 317 | Program for the general case, and put the specifics outside the compiled code base. 318 | 319 | ### Design Using Services 320 | 321 | Design in terms of services—independent, concurrent objects behind well-defined, consistent interfaces. 322 | 323 | ### Separate Views from Models 324 | 325 | Gain flexibility at low cost by designing your application in terms of models and views. 326 | 327 | ### Don’t Program by Coincidence 328 | 329 | Rely only on reliable things. Beware of accidental complexity, and don’t confuse a happy coincidence with a purposeful plan. 330 | 331 | ### Test Your Estimates 332 | 333 | Mathematical analysis of algorithms doesn’t tell you everything. Try timing your code in its target environment. 334 | 335 | ### Design to Test 336 | 337 | Start thinking about testing before you write a line of code. 338 | 339 | ### Don’t Use Wizard Code You Don’t Understand 340 | 341 | Wizards can generate reams of code. Make sure you understand all of it before you incorporate it into your project. 342 | 343 | ### Work with a User to Think Like a User 344 | 345 | It’s the best way to gain insight into how the system will really be used. 346 | 347 | ### Use a Project Glossary 348 | 349 | Create and maintain a single source of all the specific terms and vocabulary for a project. 350 | 351 | ### Start When You’re Ready 352 | 353 | You’ve been building experience all your life. Don’t ignore niggling doubts. 354 | 355 | ### Don’t Be a Slave to Formal Methods 356 | 357 | Don’t blindly adopt any technique without putting it into the context of your development practices and capabilities. 358 | 359 | ### Organize Teams Around Functionality 360 | 361 | Don’t separate designers from coders, testers from data modelers. Build teams the way you build code. 362 | 363 | ### Test Early. Test Often. Test Automatically. 364 | 365 | Tests that run with every build are much more effective than test plans that sit on a shelf. 366 | 367 | ### Use Saboteurs to Test Your Testing 368 | 369 | Introduce bugs on purpose in a separate copy of the source to verify that testing will catch them. 370 | 371 | ### Find Bugs Once 372 | 373 | Once a human tester finds a bug, it should be the last time a human tester finds that bug. Automatic tests should check for it from then on. 374 | 375 | ### Build Documentation In, Don’t Bolt It On 376 | 377 | Documentation created separately from code is less likely to be correct and up to date. 378 | 379 | ### Sign Your Work 380 | 381 | Craftsmen of an earlier age were proud to sign their work. You should be, too. 382 | --------------------------------------------------------------------------------