├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── JavaScript ├── 01-Common │ ├── 1-bad-names.js │ ├── 2-magic-num.js │ ├── 3-hard-code.js │ ├── 4-duplicate.js │ ├── 5-cryptic-code.js │ ├── 6-copy-paste.js │ ├── 7-improbability.js │ ├── 8-foolproof.js │ ├── 9-spaghetti.js │ ├── a-comments.js │ ├── b-silver-bullet.js │ ├── c-nih.js │ ├── config.js │ └── d-complexity.js ├── 02-Procedural │ ├── 1-unreachable-code.js │ ├── 2-dead-code.js │ ├── 3-switch.js │ ├── 4-parameters.js │ ├── 5-accumulate-fire.js │ ├── 6-micro-opt.js │ ├── 7-nested-loops.js │ ├── 8-long.js │ └── cities.csv └── 03-OOP │ ├── 0-simple.md │ ├── 1-large-class.js │ ├── 2-temp-field.js │ ├── 3-lazy-class.js │ ├── 4-data-clump.js │ ├── 5-feature-envy.js │ └── person.json ├── LICENSE └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "globals": { 12 | "BigInt": true 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-loop-func": [ 32 | "error" 33 | ], 34 | "block-spacing": [ 35 | "error", 36 | "always" 37 | ], 38 | "camelcase": [ 39 | "error" 40 | ], 41 | "eqeqeq": [ 42 | "error", 43 | "always" 44 | ], 45 | "strict": [ 46 | "error", 47 | "global" 48 | ], 49 | "brace-style": [ 50 | "error", 51 | "1tbs", 52 | { 53 | "allowSingleLine": true 54 | } 55 | ], 56 | "comma-style": [ 57 | "error", 58 | "last" 59 | ], 60 | "comma-spacing": [ 61 | "error", 62 | { 63 | "before": false, 64 | "after": true 65 | } 66 | ], 67 | "eol-last": [ 68 | "error" 69 | ], 70 | "func-call-spacing": [ 71 | "error", 72 | "never" 73 | ], 74 | "key-spacing": [ 75 | "error", 76 | { 77 | "beforeColon": false, 78 | "afterColon": true, 79 | "mode": "minimum" 80 | } 81 | ], 82 | "keyword-spacing": [ 83 | "error", 84 | { 85 | "before": true, 86 | "after": true, 87 | "overrides": { 88 | "function": { 89 | "after": false 90 | } 91 | } 92 | } 93 | ], 94 | "max-len": [ 95 | "error", 96 | { 97 | "code": 80, 98 | "ignoreUrls": true 99 | } 100 | ], 101 | "max-nested-callbacks": [ 102 | "error", 103 | { 104 | "max": 7 105 | } 106 | ], 107 | "new-cap": [ 108 | "error", 109 | { 110 | "newIsCap": true, 111 | "capIsNew": false, 112 | "properties": true 113 | } 114 | ], 115 | "new-parens": [ 116 | "error" 117 | ], 118 | "no-lonely-if": [ 119 | "error" 120 | ], 121 | "no-trailing-spaces": [ 122 | "error" 123 | ], 124 | "no-unneeded-ternary": [ 125 | "error" 126 | ], 127 | "no-whitespace-before-property": [ 128 | "error" 129 | ], 130 | "object-curly-spacing": [ 131 | "error", 132 | "always" 133 | ], 134 | "operator-assignment": [ 135 | "error", 136 | "always" 137 | ], 138 | "operator-linebreak": [ 139 | "error", 140 | "after" 141 | ], 142 | "semi-spacing": [ 143 | "error", 144 | { 145 | "before": false, 146 | "after": true 147 | } 148 | ], 149 | "space-before-blocks": [ 150 | "error", 151 | "always" 152 | ], 153 | "space-before-function-paren": [ 154 | "error", 155 | { 156 | "anonymous": "never", 157 | "named": "never", 158 | "asyncArrow": "always" 159 | } 160 | ], 161 | "space-in-parens": [ 162 | "error", 163 | "never" 164 | ], 165 | "space-infix-ops": [ 166 | "error" 167 | ], 168 | "space-unary-ops": [ 169 | "error", 170 | { 171 | "words": true, 172 | "nonwords": false, 173 | "overrides": { 174 | "typeof": false 175 | } 176 | } 177 | ], 178 | "no-unreachable": [ 179 | "error" 180 | ], 181 | "no-global-assign": [ 182 | "error" 183 | ], 184 | "no-self-compare": [ 185 | "error" 186 | ], 187 | "no-unmodified-loop-condition": [ 188 | "error" 189 | ], 190 | "no-constant-condition": [ 191 | "error", 192 | { 193 | "checkLoops": false 194 | } 195 | ], 196 | "no-console": [ 197 | "off" 198 | ], 199 | "no-useless-concat": [ 200 | "error" 201 | ], 202 | "no-useless-escape": [ 203 | "error" 204 | ], 205 | "no-shadow-restricted-names": [ 206 | "error" 207 | ], 208 | "no-use-before-define": [ 209 | "error", 210 | { 211 | "functions": false 212 | } 213 | ], 214 | "arrow-parens": [ 215 | "error", 216 | "always" 217 | ], 218 | "arrow-body-style": [ 219 | "error", 220 | "as-needed" 221 | ], 222 | "arrow-spacing": [ 223 | "error" 224 | ], 225 | "no-confusing-arrow": [ 226 | "error", 227 | { 228 | "allowParens": true 229 | } 230 | ], 231 | "no-useless-computed-key": [ 232 | "error" 233 | ], 234 | "no-useless-rename": [ 235 | "error" 236 | ], 237 | "no-var": [ 238 | "error" 239 | ], 240 | "object-shorthand": [ 241 | "error", 242 | "always" 243 | ], 244 | "prefer-arrow-callback": [ 245 | "error" 246 | ], 247 | "prefer-const": [ 248 | "error" 249 | ], 250 | "prefer-numeric-literals": [ 251 | "error" 252 | ], 253 | "prefer-rest-params": [ 254 | "error" 255 | ], 256 | "prefer-spread": [ 257 | "error" 258 | ], 259 | "rest-spread-spacing": [ 260 | "error", 261 | "never" 262 | ], 263 | "template-curly-spacing": [ 264 | "error", 265 | "never" 266 | ], 267 | "consistent-return": [ 268 | "error", 269 | { "treatUndefinedAsUnspecified": true } 270 | ] 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /JavaScript/01-Common/1-bad-names.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Short identifiers 4 | { 5 | const n = 'Marcus Aurelius'; 6 | } 7 | 8 | // Antipattern: Long identifiers 9 | { 10 | const romanEmperorAndOutstandingThinker = 'Marcus Aurelius'; 11 | } 12 | 13 | // Antipattern: Same name but different meaning 14 | { 15 | const name = 'Marcus Aurelius'; 16 | if (name) { 17 | const name = './config.js'; 18 | console.dir({ name }); 19 | } 20 | } 21 | 22 | // Antipattern: Same meaning but different naming 23 | { 24 | const fileName = './config.js'; 25 | if (!fileName) { 26 | const file = './backup/config.js'; 27 | if (!file) { 28 | const filePath = '../config.js'; 29 | console.dir({ filePath }); 30 | } 31 | } 32 | } 33 | 34 | // Antipattern: Inconsistent names 35 | { 36 | const api = { 37 | setPort: () => {}, 38 | assignAddress: () => {}, 39 | definePath: () => {}, 40 | }; 41 | console.dir({ api }); 42 | } 43 | 44 | // Antipattern: Non descriptive names 45 | { 46 | class ApplicationController { 47 | constructor(link) { 48 | this.link = link; 49 | } 50 | 51 | execute(handler) { 52 | this.link(handler); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /JavaScript/01-Common/2-magic-num.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Magic number 4 | { 5 | const name = 'Marcus Aurelius'; 6 | const result = name.padStart(17); 7 | console.log(result); 8 | } 9 | 10 | // Solution 11 | { 12 | const NAME_LENGTH = 17; 13 | const name = 'Marcus Aurelius'; 14 | const result = name.padStart(NAME_LENGTH); 15 | console.log(result); 16 | } 17 | 18 | // Solution 19 | { 20 | const PAD_LENGTH = 2; 21 | const name = 'Marcus Aurelius'; 22 | const result = name.padStart(name.length + PAD_LENGTH); 23 | console.log(result); 24 | } 25 | 26 | // Solution 27 | { 28 | const config = require('./config.js'); 29 | const name = 'Marcus Aurelius'; 30 | const result = name.padStart(config.name.length); 31 | console.log(result); 32 | } 33 | 34 | // Antipattern: : Magic string 35 | { 36 | const pos = '\x1b[2;10H'; 37 | console.log(pos); 38 | } 39 | 40 | // Solution 41 | { 42 | const pos = (row, col) => console.log(`\x1b[${row};${col}H`); 43 | pos(2, 10); 44 | } 45 | 46 | // Exception 47 | { 48 | const name = 'Marcus Aurelius'; 49 | for (let i = 0; i < name.length; i++) { 50 | console.log(i, name[i]); 51 | } 52 | } 53 | 54 | // Exception 55 | { 56 | const last = (array) => array[array.length - 1]; 57 | const array = ['A', 'B', 'C']; 58 | console.log(last(array)); 59 | } 60 | -------------------------------------------------------------------------------- /JavaScript/01-Common/3-hard-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs').promises; 4 | 5 | // Antipattern: Hard code 6 | (async () => { 7 | const data = await fs.readFile('./2-hard-code.js', 'utf8'); 8 | const lines = data.split('\n'); 9 | const str = lines[6]; 10 | const fileName = str.substring(34, 50); 11 | console.dir({ fileName }); 12 | })(); 13 | 14 | // Solution 15 | (async () => { 16 | 17 | const LINE_SEPARATOR = '\n'; 18 | const FILE_ENCODING = 'utf8'; 19 | 20 | const readFileBlock = async (name, line, start, end) => { 21 | const data = await fs.readFile(name, FILE_ENCODING); 22 | const lines = data.split(LINE_SEPARATOR); 23 | const str = lines[line]; 24 | return str.substring(start, end); 25 | }; 26 | 27 | const block = await readFileBlock('./2-hard-code.js', 6, 34, 50); 28 | console.dir({ block }); 29 | })(); 30 | -------------------------------------------------------------------------------- /JavaScript/01-Common/4-duplicate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { promises, watch } = require('node:fs'); 4 | const { readFile, readdir } = promises; 5 | const fs = { watch, readFile, readdir }; 6 | 7 | // Antipattern: Duplicated code 8 | { 9 | const cache = new Map(); 10 | 11 | const watchFolder = (path) => { 12 | fs.watch(path, async (event, file) => { 13 | try { 14 | const data = await fs.readFile(file, 'utf8'); 15 | cache.set(file, data); 16 | } catch (err) { 17 | cache.delete(file); 18 | } 19 | }); 20 | }; 21 | 22 | const cacheFolder = async (path) => { 23 | watchFolder(path); 24 | const files = await fs.readdir(path); 25 | for (const file of files) { 26 | try { 27 | const data = fs.readFile(file, 'utf8'); 28 | cache.set(file, data); 29 | } catch (err) { 30 | cache.delete(file); 31 | } 32 | } 33 | }; 34 | 35 | cacheFolder('./'); 36 | } 37 | 38 | // Refactored 39 | { 40 | const cache = new Map(); 41 | 42 | const cacheFile = async (file) => { 43 | try { 44 | const data = await fs.readFile(file, 'utf8'); 45 | cache.set(file, data); 46 | } catch (err) { 47 | cache.delete(file); 48 | } 49 | }; 50 | 51 | const watchFolder = (path) => { 52 | fs.watch(path, (event, file) => { 53 | cacheFile(file); 54 | }); 55 | }; 56 | 57 | const cacheFolder = async (path) => { 58 | watchFolder(path); 59 | const files = await fs.readdir(path); 60 | for (const file of files) cacheFile(file); 61 | }; 62 | 63 | cacheFolder('./'); 64 | } 65 | -------------------------------------------------------------------------------- /JavaScript/01-Common/5-cryptic-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Cryptic code 4 | 5 | const emitter = (l, o) => (l = {}, o = { 6 | on: (n, f) => (l[n] = l[n] || []).push(f), 7 | emit: (n, ...d) => (l[n] || []).map((f) => f(...d)), 8 | once: (n, f, g) => o.on(n, g = (...a) => (f(...a), o.remove(n, g))), 9 | remove: (n, f, e) => (e = l[n] || [], e.splice(e.indexOf(f), 1)), 10 | clear: (n) => (n ? delete l[n] : l = {}), 11 | count: (n) => (l[n] || []).length, 12 | listeners: (n) => (l[n] || []).slice(), 13 | names: () => Object.keys(l) 14 | }); 15 | 16 | // Usage 17 | 18 | const ee = emitter(); 19 | 20 | ee.on('e1', (data) => { 21 | console.dir(data); 22 | }); 23 | 24 | ee.emit('e1', { msg: 'e1 ok' }); 25 | -------------------------------------------------------------------------------- /JavaScript/01-Common/6-copy-paste.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Copy and paste 4 | 5 | // Google: javascript get first element of array 6 | // First result: 7 | // https://stackoverflow.com/questions/4090491/get-first-element-in-array 8 | 9 | // I tried this. But it would return [object Object]. 10 | { 11 | alert($(ary).first()); 12 | } 13 | 14 | // Approved 15 | { 16 | alert(ary[0]) 17 | } 18 | 19 | // Patch built-in class 20 | { 21 | Array.prototype.first = function() { 22 | return this[0]; 23 | }; 24 | } 25 | 26 | // Another solution 27 | { 28 | array.find((e) => !!e); 29 | } 30 | -------------------------------------------------------------------------------- /JavaScript/01-Common/7-improbability.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | 5 | // Antipattern: Improbability 6 | // Assumption: file always exists 7 | { 8 | fs.readFile('./filename.ext', 'utf8', (err, data) => { 9 | const found = data.includes('substring'); 10 | console.dir({ found }); 11 | }); 12 | } 13 | 14 | // Antipattern: Improbability 15 | // Assumption: json format, field `port` exists and type is `number` 16 | { 17 | fs.readFile('./config.js', 'utf8', (err, data) => { 18 | const config = JSON.parse(data); 19 | const { port } = config; 20 | console.dir({ port }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /JavaScript/01-Common/8-foolproof.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Fool-proof code 4 | // Assumption: idiots will pass anything to my function 5 | { 6 | const max = (...args) => { 7 | if (args.length !== 2) { 8 | throw new Error('Function expects two aruments'); 9 | } 10 | const a = args.shift(); 11 | if (typeof a !== 'number') { 12 | throw new Error('Unexpected type of first arument'); 13 | } 14 | const b = args.shift(); 15 | if (typeof b !== 'number') { 16 | throw new Error('Unexpected type of second arument'); 17 | } 18 | return a > b ? a : b; 19 | }; 20 | 21 | // Usage 1 22 | console.log(`Max of 10 and 20 is ${max(10, 20)}`); 23 | 24 | // Usage 2 25 | const a = new Number(10); 26 | const b = new Number(20); 27 | console.log(`Max of ${a} and ${b} is ${max(a, b)}`); 28 | 29 | // Usage 3 30 | const x = { 31 | [Symbol.toPrimitive]() { 32 | return 10; 33 | } 34 | }; 35 | const y = 20; 36 | console.log(`Max of ${x} and ${y} is ${max(x, y)}`); 37 | } 38 | 39 | // Solution 40 | { 41 | const max = (a, b) => (a > b ? a : b); 42 | 43 | // Usage 1 44 | console.log(`Max of 10 and 20 is ${max(10, 20)}`); 45 | 46 | // Usage 2 47 | const a = new Number(10); 48 | const b = new Number(20); 49 | console.log(`Max of ${a} and ${b} is ${max(a, b)}`); 50 | 51 | // Usage 3 52 | const x = { 53 | [Symbol.toPrimitive]() { 54 | return 10; 55 | } 56 | }; 57 | const y = 20; 58 | console.log(`Max of ${x} and ${y} is ${max(x, y)}`); 59 | } 60 | -------------------------------------------------------------------------------- /JavaScript/01-Common/9-spaghetti.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Spaghetti code 4 | // - JMP, GOTO 5 | // - Callbacks 6 | // - Events 7 | // - Promises 8 | 9 | // Callbacks 10 | { 11 | const invoke = (validate, fn, a, b, callback) => { 12 | const result = fn(validate, a, b); 13 | callback(null, result); 14 | }; 15 | 16 | const max = (validate, a, b) => { 17 | let valid = true; 18 | const conjunction = (err, res) => { 19 | valid = valid && res; 20 | }; 21 | validate(a, conjunction); 22 | validate(b, conjunction); 23 | 24 | if (!valid) throw new TypeError('Unexpected parameter'); 25 | 26 | return Math.max(a, b); 27 | }; 28 | 29 | const isNumber = (value, callback) => { 30 | const valid = typeof value === 'number'; 31 | callback(null, valid); 32 | }; 33 | 34 | invoke(isNumber, max, 10, 20, (err, result) => { 35 | console.dir({ result }); 36 | }); 37 | } 38 | 39 | // Events 40 | // Try to debug this code to find logical error 41 | { 42 | const { EventEmitter } = require('node:events'); 43 | 44 | const incoming = new EventEmitter(); 45 | const controller = new EventEmitter(); 46 | const processing = new EventEmitter(); 47 | 48 | const parameters = []; 49 | 50 | incoming.on('return', (result) => { 51 | console.dir({ result }); 52 | }); 53 | 54 | processing.on('max', (a, b) => { 55 | incoming.emit('return', Math.max(a, b)); 56 | }); 57 | 58 | controller.on('parameter', (value) => { 59 | parameters.push(value); 60 | }); 61 | 62 | incoming.on('input', (data) => { 63 | if (typeof data === 'string') { 64 | controller.emit('call', data); 65 | } else { 66 | controller.emit('parameter', data); 67 | } 68 | }); 69 | 70 | controller.on('call', (name) => { 71 | processing.emit(name, ...parameters); 72 | }); 73 | 74 | incoming.emit('input', 10); 75 | incoming.emit('input', 20); 76 | incoming.emit('input', 'max'); 77 | } 78 | -------------------------------------------------------------------------------- /JavaScript/01-Common/a-comments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Comments 4 | // Refactor the code so the comments may be removed 5 | 6 | { 7 | // Maybe is a recursive closure 8 | // if x and f are defined this will calculate f(x) 9 | // and return new instance of functional object 10 | const m = (x) => (f) => m(x && f ? f(x) : null); 11 | 12 | // So we can define a chain of value transformations 13 | m(5)((x) => x * 2)((x) => ++x)(console.log); 14 | } 15 | 16 | { 17 | const maybe = (value) => ({ 18 | map(fn) { 19 | if (value && fn) return maybe(fn(value)); 20 | return maybe(null); 21 | } 22 | }); 23 | 24 | maybe(5) 25 | .map((x) => x * 2) 26 | .map((x) => ++x) 27 | .map(console.log); 28 | } 29 | -------------------------------------------------------------------------------- /JavaScript/01-Common/b-silver-bullet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Silver bullet 4 | // Example: when you know Array methods 5 | { 6 | const name = 'Marcus Aurelius Antoninus Augustus'; 7 | const spaceCount = name.split(' ').reduce((acc) => acc + 1, 0) - 1; 8 | console.log({ spaceCount }); 9 | } 10 | 11 | // Solution 12 | { 13 | const name = 'Marcus Aurelius Antoninus Augustus'; 14 | let spaceCount = 0; 15 | for (const char of name) { 16 | if (char === ' ') spaceCount++; 17 | } 18 | console.log({ spaceCount }); 19 | } 20 | 21 | // Antipattern: Silver bullet 22 | // Example: when you know Promises 23 | { 24 | const spaceCount = (str) => new Promise((resolve) => { 25 | let count = 0; 26 | for (const char of str) { 27 | if (char === ' ') count++; 28 | } 29 | resolve(count); 30 | }); 31 | 32 | const name = 'Marcus Aurelius Antoninus Augustus'; 33 | spaceCount(name).then((count) => console.log({ spaceCount: count })); 34 | } 35 | 36 | // Solution 37 | { 38 | const spaceCount = (str) => { 39 | let count = 0; 40 | for (const char of str) { 41 | if (char === ' ') count++; 42 | } 43 | return count; 44 | }; 45 | 46 | const name = 'Marcus Aurelius Antoninus Augustus'; 47 | console.log({ spaceCount: spaceCount(name) }); 48 | } 49 | -------------------------------------------------------------------------------- /JavaScript/01-Common/c-nih.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Not Invented Here 4 | // 5 | // Advantages 6 | // - No dependencies - no problems (remember left-pad) 7 | // - Security 8 | // 9 | // Disadvantages 10 | // - Waste of time 11 | // - Solution quality 12 | -------------------------------------------------------------------------------- /JavaScript/01-Common/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: { 5 | length: 17 6 | }, 7 | fileName: '2-hard-code.js', 8 | }; 9 | -------------------------------------------------------------------------------- /JavaScript/01-Common/d-complexity.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Accidental complexity 4 | 5 | class Application { 6 | static async main() { 7 | console.log('Hello world'); 8 | } 9 | } 10 | 11 | Application.main(); 12 | 13 | // Best practice of middleware boilerplate 14 | 15 | router.get('/user/:id', (req, res) => { 16 | const id = parseInt(req.params.id); 17 | const query = 'SELECT * FROM users WHERE id = $1'; 18 | pool.query(query, [id], (err, data) => { 19 | if (err) throw err; 20 | res.status(200).json(data.rows); 21 | }); 22 | }); 23 | 24 | // AWS boilerplate 25 | 26 | exports.handler = (event, context, callback) => { 27 | const { Client } = require('pg'); 28 | const client = new Client(); 29 | client.connect(); 30 | const id = parseInt(event.pathParameters.id); 31 | const query = 'SELECT * FROM users WHERE id = $1'; 32 | client.query(query, [id], (err, data) => { 33 | callback(err, { 34 | statusCode: 200, 35 | body: JSON.stringify(data.rows), 36 | }); 37 | }); 38 | }; 39 | 40 | // The essence 41 | 42 | async (id) => await application.database 43 | .select('users') 44 | .where({ id }); 45 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/1-unreachable-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Unreachable code 4 | // linter detectable 5 | { 6 | const isValid = (name) => { 7 | return true; 8 | // everything after return is unreachable 9 | if (name.includes(' ')) return false; 10 | }; 11 | 12 | console.log(isValid('Marcus Aurelius')); 13 | } 14 | 15 | // Antipattern: Unreachable code 16 | // run-time or analytic 17 | { 18 | const isValid = (name) => { 19 | if (!name) return false; 20 | if (name.length === 0) return false; 21 | if (name.includes(' ')) return false; 22 | if (name[0] === ' ') return false; // unreachable 23 | return true; 24 | }; 25 | 26 | console.log(isValid('Marcus Aurelius')); 27 | } 28 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/2-dead-code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Dead code 4 | { 5 | const isValid = (name) => { 6 | const parts = name.split(' '); 7 | if (parts.length > 0) parts.push('Last'); 8 | else parts.unshift('First'); 9 | return true; 10 | }; 11 | 12 | console.log(isValid('Marcus Aurelius')); 13 | } 14 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/3-switch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: switch 4 | { 5 | const color = (name) => { 6 | switch (name) { 7 | case 'black': 8 | console.log(1); 9 | break; 10 | case 'red': 11 | console.log(2); 12 | break; 13 | case 'green': 14 | console.log(3); 15 | break; 16 | case 'yellow': 17 | console.log(4); 18 | break; 19 | case 'blue': 20 | console.log(5); 21 | break; 22 | case 'magenta': 23 | console.log(6); 24 | break; 25 | case 'cyan': 26 | console.log(7); 27 | break; 28 | case 'white': 29 | console.log(8); 30 | break; 31 | } 32 | console.log('color name:', name); 33 | }; 34 | 35 | color('white'); 36 | } 37 | 38 | // Better switch usage 39 | { 40 | const color = (name) => { 41 | switch (name) { 42 | case 'black': return 1; 43 | case 'red': return 2; 44 | case 'green': return 3; 45 | case 'yellow': return 4; 46 | case 'blue': return 5; 47 | case 'magenta': return 6; 48 | case 'cyan': return 7; 49 | case 'white': return 8; 50 | } 51 | }; 52 | 53 | console.log('white', color('white')); 54 | } 55 | 56 | // Use array instead 57 | { 58 | const COLORS = [ 59 | /* 1 */ 'black', 60 | /* 2 */ 'red', 61 | /* 3 */ 'green', 62 | /* 4 */ 'yellow', 63 | /* 5 */ 'blue', 64 | /* 6 */ 'magenta', 65 | /* 7 */ 'cyan', 66 | /* 8 */ 'white', 67 | ]; 68 | const color = (name) => COLORS.indexOf(name) + 1; 69 | 70 | console.log('white', color('white')); 71 | } 72 | 73 | // Use object instead 74 | { 75 | const COLORS = { 76 | black: 1, 77 | red: 2, 78 | green: 3, 79 | yellow: 4, 80 | blue: 5, 81 | magenta: 6, 82 | cyan: 7, 83 | white: 8, 84 | }; 85 | const color = (name) => COLORS[name]; 86 | 87 | console.log('white', color('white')); 88 | } 89 | 90 | // Use Map instead 91 | { 92 | const COLORS = new Map([ 93 | ['black', 1], 94 | ['red', 2], 95 | ['green', 3], 96 | ['yellow', 4], 97 | ['blue', 5], 98 | ['magenta', 6], 99 | ['cyan', 7], 100 | ['white', 8], 101 | ]); 102 | const color = (name) => COLORS.get(name); 103 | 104 | console.log('white', color('white')); 105 | } 106 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/4-parameters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Too many parameters 4 | // Antipattern: Pass-through parameters 5 | { 6 | const validatePerson = (name, city, birth, dynasty, school) => { 7 | if (name.length < 3) return false; 8 | if (!city) return false; 9 | if (Number.isNaN(Date.parse(birth))) return false; 10 | if (dynasty.length < 5) return false; 11 | if (dynasty === school) return false; 12 | return true; 13 | }; 14 | 15 | const buildPerson = (name, city, birth, dynasty, school) => { 16 | const date = new Date(birth); 17 | return { name, city, birth: date, dynasty, school }; 18 | }; 19 | 20 | const serializePerson = ({ name, city, birth, dynasty, school }) => { 21 | const date = birth.toISOString(); 22 | return `${name} [${dynasty}] from ${city} (${date}) ${school} school`; 23 | }; 24 | 25 | const registerPerson = (name, city, birth, dynasty, school) => { 26 | const valid = validatePerson(name, city, birth, dynasty, school); 27 | if (!valid) throw new Error('Emperor is invalid'); 28 | const person = buildPerson(name, city, birth, dynasty, school); 29 | const data = serializePerson(person); 30 | console.log(data); 31 | }; 32 | 33 | registerPerson( 34 | 'Marcus Aurelius', 35 | 'Rome', 36 | '212-04-26', 37 | 'Nerva-Antonine', 38 | 'Stoicism', 39 | ); 40 | } 41 | 42 | // Solution 43 | { 44 | const validatePerson = ({ name, city, birth, dynasty, school }) => { 45 | if (name.length < 3) return false; 46 | if (!city) return false; 47 | if (Number.isNaN(Date.parse(birth))) return false; 48 | if (dynasty.length < 5) return false; 49 | if (dynasty === school) return false; 50 | return true; 51 | }; 52 | 53 | const serializePerson = ({ name, city, birth, dynasty, school }) => { 54 | const date = new Date(birth).toISOString(); 55 | return `${name} [${dynasty}] from ${city} (${date}) ${school} school`; 56 | }; 57 | 58 | const registerPerson = (person) => { 59 | const valid = validatePerson(person); 60 | if (!valid) throw new Error('Emperor is invalid'); 61 | const data = serializePerson(person); 62 | console.log(data); 63 | }; 64 | 65 | registerPerson({ 66 | name: 'Marcus Aurelius', 67 | city: 'Rome', 68 | birth: '212-04-26', 69 | dynasty: 'Nerva-Antonine', 70 | school: 'Stoicism', 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/5-accumulate-fire.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Accumulate and fire 4 | { 5 | const name = 'Marcus Aurelius'; 6 | const city = 'Rome'; 7 | const birth = '212-04-26'; 8 | const dynasty = 'Nerva-Antonine'; 9 | const school = 'Stoicism'; 10 | registerPerson(); 11 | 12 | function registerPerson() { 13 | const date = new Date(birth); 14 | const person = { name, city, birth: date, dynasty, school }; 15 | console.log({ person }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/6-micro-opt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Premature optimization is the root of all evil D. Knuth 4 | // Antipattern: Micro-optimization 5 | { 6 | 7 | const range = (from, to) => { 8 | const arr = []; 9 | for (let i = from; i <= to; i++) arr.push(i); 10 | return arr; 11 | }; 12 | 13 | const range2 = (from, to) => { 14 | if (to < from) return []; 15 | const len = to - from + 1; 16 | const range = new Array(len); 17 | for (let i = from; i <= to; i++) { 18 | range[i - from] = i; 19 | } 20 | return range; 21 | }; 22 | 23 | const range3 = (from, to) => { 24 | if (to < from) return []; 25 | const len = to - from + 1; 26 | const range = new Array(len); 27 | let index = 0; 28 | for (let i = from; i <= to; i++) { 29 | range[index] = i; 30 | index++; 31 | } 32 | return range; 33 | }; 34 | 35 | const range4 = (from, to) => { 36 | if (to < from) return []; 37 | const range = [0]; 38 | range.length = to - from + 1; 39 | let index = 0; 40 | for (let i = from; i <= to; i++) { 41 | range[index] = i; 42 | index++; 43 | } 44 | return range; 45 | }; 46 | 47 | const range5 = (from, to) => { 48 | if (to < from) return []; 49 | const len = to - from + 1; 50 | const range = new Array(len).fill(0); 51 | let index = 0; 52 | for (let i = from; i <= to; i++) { 53 | range[index] = i; 54 | index++; 55 | } 56 | return range; 57 | }; 58 | 59 | // Micro-benchmarking utilities 60 | 61 | const rpad = (s, char, count) => (s + char.repeat(count - s.length)); 62 | const lpad = (s, char, count) => (char.repeat(count - s.length) + s); 63 | const relativePercent = (best, time) => (time * 100n / best) - 100n; 64 | 65 | const benchmark = (count, args, tests) => { 66 | const times = tests.map((fn) => { 67 | const result = []; 68 | const begin = process.hrtime.bigint(); 69 | for (let i = 0; i < count; i++) result.push(fn(...args)); 70 | const end = process.hrtime.bigint(); 71 | const diff = end - begin; 72 | const name = rpad(fn.name, '.', 22); 73 | const iterations = result.length; 74 | const log = [name, diff]; 75 | console.log(log.join(' ')); 76 | return { name, time: diff }; 77 | }); 78 | console.log(); 79 | const top = times.sort((t1, t2) => Number(t1.time - t2.time)); 80 | const best = top[0].time; 81 | top.forEach((test) => { 82 | test.percent = relativePercent(best, test.time); 83 | const time = lpad(test.time.toString(), '.', 10); 84 | const percent = test.percent === 0 ? 'min' : test.percent + '%'; 85 | const line = lpad(percent, '.', 10); 86 | console.log(test.name + time + line); 87 | }); 88 | }; 89 | 90 | // Micro-benchmarking 91 | 92 | benchmark( 93 | 1000000, 94 | [1, 100], 95 | [range, range2, range3, range4, range5] 96 | ); 97 | 98 | } 99 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/7-nested-loops.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Nested loops 4 | // Antipattern: Pyramid of doom 5 | { 6 | const phones = [ 7 | { name: 'Marcus', phone: '12345678' }, 8 | { name: 'Kant', phone: '1234567' }, 9 | ]; 10 | 11 | const cities = [ 12 | { name: 'Marcus', city: 'Roma' }, 13 | { name: 'Kant', city: 'Kaliningrad' }, 14 | ]; 15 | 16 | const prefixes = [ 17 | { name: 'Roma', prefix: '+3906' }, 18 | { name: 'Kaliningrad', prefix: '+7401' }, 19 | ]; 20 | 21 | const getPhoneNumber = (name) => { 22 | for (const record1 of phones) { 23 | if (record1.name === name) { 24 | for (const record2 of cities) { 25 | if (record2.name === name) { 26 | for (const record3 of prefixes) { 27 | if (record3.name === record2.city) { 28 | return record3.prefix + record1.phone; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | }; 36 | 37 | const callMarcus = getPhoneNumber('Marcus'); 38 | console.dir({ callMarcus }); 39 | } 40 | 41 | // Antipattern: Hidden loops 42 | { 43 | const persons = [ 44 | { name: 'Marcus', phone: '12345678', city: 'Roma' }, 45 | { name: 'Kant', phone: '1234567', city: 'Kaliningrad' }, 46 | ]; 47 | 48 | const prefixes = { 49 | Roma: '+3906', 50 | Kaliningrad: '+7401', 51 | }; 52 | 53 | const getPhoneNumber = (name) => { 54 | const person = persons.find((person) => person.name === name); 55 | const { phone, city } = person; 56 | const prefix = prefixes[city]; 57 | return prefix + phone; 58 | }; 59 | 60 | const callMarcus = getPhoneNumber('Marcus'); 61 | console.dir({ callMarcus }); 62 | } 63 | 64 | // Solution 65 | { 66 | const persons = { 67 | Marcus: { name: 'Marcus', phone: '12345678', city: 'Roma' }, 68 | Kant: { name: 'Kant', phone: '1234567', city: 'Kaliningrad' }, 69 | }; 70 | 71 | const prefixes = { 72 | Roma: '+3906', 73 | Kaliningrad: '+7401', 74 | }; 75 | 76 | const getPhoneNumber = (name) => { 77 | const person = persons[name]; 78 | const { phone, city } = person; 79 | const prefix = prefixes[city]; 80 | return prefix + phone; 81 | }; 82 | 83 | const callMarcus = getPhoneNumber('Marcus'); 84 | console.dir({ callMarcus }); 85 | } 86 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/8-long.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | 5 | // Antipattern: Long method, function, or procedure 6 | 7 | const processCities = (data) => { 8 | const lines = data.split('\n'); 9 | lines.pop(); 10 | const table = []; 11 | let first = true; 12 | let max = 0; 13 | for (const line of lines) { 14 | if (first) { 15 | first = false; 16 | } else { 17 | const cells = line.split(','); 18 | const d = parseInt(cells[3]); 19 | if (d > max) max = d; 20 | table.push([cells[0], cells[1], cells[2], cells[3], cells[4]]); 21 | } 22 | } 23 | for (const row of table) { 24 | const a = Math.round(row[3] * 100 / max); 25 | row.push(a.toString()); 26 | } 27 | table.sort((r1, r2) => r2[5] - r1[5]); 28 | for (const row of table) { 29 | let s = row[0].padEnd(18); 30 | s += row[1].padStart(10); 31 | s += row[2].padStart(8); 32 | s += row[3].padStart(8); 33 | s += row[4].padStart(18); 34 | s += row[5].padStart(6); 35 | console.log(s); 36 | } 37 | }; 38 | 39 | const data = fs.readFileSync('./cities.csv', 'utf8'); 40 | processCities(data); 41 | -------------------------------------------------------------------------------- /JavaScript/02-Procedural/cities.csv: -------------------------------------------------------------------------------- 1 | city,population,area,density,country 2 | Shanghai,24256800,6340,3826,China 3 | Beijing,21516000,16411,1311,China 4 | Delhi,16787941,1484,11313,India 5 | Lagos,16060303,1171,13712,Nigeria 6 | Tianjin,15200000,11760,1293,China 7 | Karachi,14910352,3527,4572,Pakistan 8 | Istanbul,14160467,5461,2593,Turkey 9 | Tokyo,13513734,2191,6168,Japan 10 | Guangzhou,13080500,7434,1759,China 11 | Mumbai,12442373,6034,20680,India 12 | Moscow,12380664,2510,4859,Russia 13 | Sao Paulo,12038175,1521,7914,Brazil 14 | Lahore,11126285,1772,5673,Pakistan 15 | Shenzhen,10467400,1992,5256,China 16 | Jakarta,10075310,664,15171,Indonesia 17 | Seoul,9995784,605,17134,South Korea 18 | Wuhan,9785392,1328,5187,China 19 | Kinshasa,9735000,1118,8710,Congo 20 | Cairo,9278441,3085,3008,Egypt 21 | Dhaka,8906039,300,29687,Bangladesh 22 | Mexico City,8874724,1486,5974,Mexico 23 | Lima,8693387,2672,3253,Peru 24 | London,8673713,1572,5431,United Kingdom 25 | New York City,8537673,784,10892,United States 26 | Bengaluru,8443675,741,11876,India 27 | Bangkok,8280925,1569,5279,Thailand 28 | Ho Chi Minh City,8224400,2096,3925,Vietnam 29 | Dongguan,8220207,2469,3329,China 30 | Chongqing,8189800,5473,1496,China 31 | Nanjing,8187828,4714,1737,China 32 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/0-simple.md: -------------------------------------------------------------------------------- 1 | ## Simple antipatterns 2 | 3 | - Duplicate code 4 | - Hardcode 5 | - Magic numbers 6 | - Criptic code 7 | - Accidental complexity 8 | - Long method 9 | - Long inheritance 10 | - Long identifier 11 | - Large class 12 | - Dead and unreachable code 13 | - Too many parameters 14 | - Pass-through parameters 15 | - Accumulate and fire 16 | - Use switch/case (or multiple if statements) 17 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/1-large-class.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('node:http'); 4 | const fs = require('node:fs'); 5 | 6 | // Antipattern: Large Class or God Object 7 | 8 | class Controller { 9 | constructor() { 10 | this.cache = {}; 11 | this.server = http.createServer((req, res) => { 12 | this.handler(req, res); 13 | }); 14 | } 15 | 16 | parseCookies(cookie) { 17 | const cookies = {}; 18 | if (cookie) cookie.split(';').forEach((item) => { 19 | const parts = item.split('='); 20 | cookies[(parts[0]).trim()] = (parts[1] || '').trim(); 21 | }); 22 | return cookies; 23 | } 24 | 25 | log(line) { 26 | const date = new Date().toISOString(); 27 | console.log(date + ' ' + line); 28 | } 29 | 30 | serveFromCache(req, res) { 31 | if (this.cache[req.url] && req.method === 'GET') { 32 | res.writeHead(200); 33 | res.end(this.cache[req.url]); 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | getRoot(req, res, cookies) { 40 | res.writeHead(200, { 41 | 'Set-Cookie': 'mycookie=test', 42 | 'Content-Type': 'text/html' 43 | }); 44 | const ip = req.connection.remoteAddress; 45 | res.write(`
${JSON.stringify(cookies)}`); 47 | } 48 | 49 | getPerson(req, res) { 50 | fs.readFile('./person.json', (err, data) => { 51 | if (!err) { 52 | const obj = JSON.parse(data); 53 | obj.birth = new Date(obj.birth); 54 | const difference = new Date() - obj.birth; 55 | obj.age = Math.floor(difference / 31536000000); 56 | delete obj.birth; 57 | const sobj = JSON.stringify(obj); 58 | this.cache[req.url] = sobj; 59 | // HTTP reply 60 | res.writeHead(200); 61 | res.end(sobj); 62 | } else { 63 | res.writeHead(500); 64 | res.end('Read error'); 65 | } 66 | }); 67 | } 68 | 69 | postPerson(req, res) { 70 | const body = []; 71 | req.on('data', (chunk) => { 72 | body.push(chunk); 73 | }).on('end', () => { 74 | let data = Buffer.concat(body).toString(); 75 | const obj = JSON.parse(data); 76 | if (obj.name) obj.name = obj.name.trim(); 77 | data = JSON.stringify(obj); 78 | this.cache[req.url] = data; 79 | fs.writeFile('./person.json', data, (err) => { 80 | if (!err) { 81 | res.writeHead(200); 82 | res.end('File saved'); 83 | } else { 84 | res.writeHead(500); 85 | res.end('Write error'); 86 | } 87 | }); 88 | }); 89 | } 90 | 91 | routing(req, res, cookies) { 92 | console.log(req.method, req.url); 93 | if (req.url === '/') { 94 | if (req.method === 'GET') { 95 | this.getRoot(req, res, cookies); 96 | } 97 | return; 98 | } else if (req.url === '/person') { 99 | if (req.method === 'GET') { 100 | this.getPerson(req, res); 101 | } else if (req.method === 'POST') { 102 | this.postPerson(req, res); 103 | } 104 | return; 105 | } 106 | res.writeHead(404); 107 | res.end('Path not found'); 108 | } 109 | 110 | handler(req, res) { 111 | const cookies = this.parseCookies(req.headers.cookie); 112 | this.log(`${req.method} ${req.url}`); 113 | if (!this.serveFromCache(req, res)) { 114 | this.routing(req, res, cookies); 115 | } 116 | } 117 | 118 | listen(port) { 119 | this.server.listen(port); 120 | } 121 | } 122 | 123 | // Usage 124 | 125 | const controller = new Controller(); 126 | controller.listen(8000); 127 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/2-temp-field.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Temporary field 4 | // Antipattern: Handle object as instances and hashes at the same time 5 | // Antipattern: Use fields instead of arguments 6 | 7 | class Person { 8 | constructor(name, birth) { 9 | this.name = name; 10 | this.birth = birth; 11 | this.parseAge(); 12 | } 13 | 14 | parseAge() { 15 | const difference = new Date() - new Date(this.birth); 16 | this.age = Math.floor(difference / 31536000000); 17 | delete this.birth; 18 | } 19 | } 20 | 21 | // Usage 22 | 23 | const person = new Person('Marcus Aurelius', '121-04-26'); 24 | console.dir({ person }); 25 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/3-lazy-class.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Lazy Class 4 | 5 | class PersonName { 6 | constructor(name) { 7 | this.name = name; 8 | } 9 | } 10 | 11 | class PersonBirth { 12 | constructor(birth) { 13 | this.birth = new Date(birth); 14 | } 15 | } 16 | 17 | class Person { 18 | constructor(name, birth) { 19 | this.name = new PersonName(name); 20 | this.birth = new PersonBirth(birth); 21 | } 22 | } 23 | 24 | // Usage 25 | 26 | const person = new Person('Marcus Aurelius', '121-04-26'); 27 | console.dir({ person }); 28 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/4-data-clump.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Data clump 4 | 5 | const countries = { 6 | 379: 'Vatican', 7 | 380: 'Ukraine', 8 | 381: 'Serbia', 9 | }; 10 | 11 | const areas = { 12 | 43: 'Vinnitsa', 13 | 44: 'Kiev', 14 | 62: 'Donetsk', 15 | }; 16 | 17 | const getCountryCode = (name) => Object 18 | .keys(countries) 19 | .find((key) => countries[key] === name); 20 | 21 | const getAreaCode = (name) => Object 22 | .keys(areas) 23 | .find((key) => areas[key] === name); 24 | 25 | const prepareCommand = (country, area, number) => { 26 | const countryCode = getCountryCode(country); 27 | const areaCode = getAreaCode(area); 28 | return `ATDP ${countryCode}${areaCode}${number}`; 29 | }; 30 | 31 | class Person { 32 | constructor(name, phoneNumber) { 33 | this.name = name; 34 | this.phone = phoneNumber; 35 | } 36 | 37 | parsePhone() { 38 | const phone = this.phone; 39 | const country = countries[phone.substring(1, 4)]; 40 | const area = areas[phone.substring(4, 6)]; 41 | const number = phone.substring(6, 13); 42 | return [country, area, number]; 43 | } 44 | 45 | isValid(country, area, number) { 46 | if (!getCountryCode(country)) return false; 47 | if (!getAreaCode(area)) return false; 48 | if (number === '') return false; 49 | return true; 50 | } 51 | 52 | call() { 53 | const [country, area, number] = this.parsePhone(); 54 | if (!this.isValid(country, area, number)) { 55 | throw new Error('Invalid phone number'); 56 | } 57 | const command = prepareCommand(country, area, number); 58 | console.log(command); 59 | } 60 | } 61 | 62 | // Usage 63 | 64 | const person = new Person('Marcus Aurelius', '+380441234567'); 65 | console.dir({ person }); 66 | person.call(); 67 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/5-feature-envy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Antipattern: Feature Envy 4 | // Antipattern: High Coupling 5 | 6 | const countries = { 7 | 379: 'Vatican', 8 | 380: 'Ukraine', 9 | 381: 'Serbia', 10 | }; 11 | 12 | const areas = { 13 | 43: 'Vinnitsa', 14 | 44: 'Kiev', 15 | 62: 'Donetsk', 16 | }; 17 | 18 | class Phone { 19 | constructor(s) { 20 | this.country = countries[s.substring(1, 4)]; 21 | this.area = areas[s.substring(4, 6)]; 22 | this.number = s.substring(6, 13); 23 | } 24 | 25 | static getCountryCode(name) { 26 | return Object.keys(countries).find((key) => countries[key] === name); 27 | } 28 | 29 | static getAreaCode(name) { 30 | return Object.keys(areas).find((key) => areas[key] === name); 31 | } 32 | } 33 | 34 | class Person { 35 | constructor(name, phoneNumber) { 36 | this.name = name; 37 | this.phone = new Phone(phoneNumber); 38 | } 39 | 40 | call() { 41 | const countryCode = Phone.getCountryCode(this.phone.country); 42 | const areaCode = Phone.getAreaCode(this.phone.area); 43 | const phone = this.phone.number; 44 | console.log(`ATDP ${countryCode}${areaCode}${phone}`); 45 | } 46 | } 47 | 48 | // Usage 49 | 50 | const person = new Person('Marcus Aurelius', '+380441234567'); 51 | console.dir({ person }); 52 | person.call(); 53 | -------------------------------------------------------------------------------- /JavaScript/03-OOP/person.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jura", 3 | "birth": "1990-02-15" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2023 How.Programming.Works contributors 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Antipatterns as a Worst Practices 2 | 3 | | [](https://youtu.be/NMUsUiFokr4) | [](https://youtu.be/cTv7V22mkwE) | 4 | |---|---| 5 | | [](https://youtu.be/9d5TG1VsLeU) | | 6 | --------------------------------------------------------------------------------