0%

Functions

Functions

  1. 함수 parameter / argument는 2개 이하

    함수 테스팅을 쉽게 만들어 주기 때문

    • 3개 이상인 경우: 통합하기

    • higher-level object의 경우 1개로 충분함

    • 객체를 생성하고 활용하기

      • 적은 boilerplate으로도 객체를 만들기 쉬우므로, argument가 많이 필요한 경우 객체를 생성해서 활용하기

      boilerplate : 변경 없이 계속 재사용할 수 있는 코드, 프로그램

      • Destructuring

        구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식입니다.

        • function signature (argument, return type) 등을 통해 어떤 값이 전달되고 이용되는지 알 수 있음
        • simulate named parameters

        named parameters : function calls that clearly state the name of each parameter within the function call

        • 전달된 argument에 default 값을 복제함, argument로부터 destructure된 object / array는 복제되지 않음
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // bad
      function createMenu(title, body, buttonText, cancellable) {
      // ...
      }

      createMenu("Foo", "Bar", "Baz", true);

      // good
      function createMenu({ title, body, buttonText, cancellable }) {
      // ...
      }

      createMenu({
      title: "Foo",
      body: "Bar",
      buttonText: "Baz",
      cancellable: true
      });
  2. 하나의 함수는 하나의 행동만

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// bad
function emailClients(clients) {
clients.forEach(client => {
// 1-1. check the client : look up the client
const clientRecord = database.lookup(client);

// 1-2. check the client : check the client status
if (clientRecord.isActive()) {

// 2. email the client
email(client);
}
});
}


// good

// 2.
function emailClients(clients) {
clients
.filter(isClientActive)
.forEach(email);
}

// 1.
function isClientActive(client) {
// 1-1.
const clientRecord = database.lookup(client);

// 1-2.
return clientRecord.isActive();
}
  1. 함수명은 명시적으로 action을 정의하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bad
function AddToDate(date, month) {
// ...
}

const date = new Date();

// 뭘 추가하는 건지 이름만 보고 알아내기 힘듭니다.
AddToDate(date, 1);


// good
function AddMonthToDate(date, month) {
// ...
}

const date = new Date();
AddMonthToDate(date, 1);
  1. (2)와 비슷한 맥락으로, 재사용 가능하고 테스트가 용이할 수 있도록 하나의 함수는 one level of abstraction을 담당하도록 한다.

❓(2)와 어떤 차이가 있는지?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// bad
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];

// 1. tokenize
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
// ...
});
});

// 2. lexer (lexical analysis)
const ast = [];
tokens.forEach(token => {
// lex...
});

// 3. parse
ast.forEach(node => {
// parse...
});
}


// good
function tokenize(code) {
const REGEXES = [
// ...
];

const statements = code.split(' ');
const tokens = [];
REGEXES.forEach(REGEX => {
statements.forEach(statement => {
tokens.push( /* ... */ );
});
});

return tokens;
}

function lexer(tokens) {
const ast = [];
tokens.forEach(token => {
ast.push( /* ... */ );
});

return ast;
}

function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const ast = lexer(tokens);
ast.forEach(node => {
// parse...
});
}
  1. 중복된 코드는 삭제하기

    • 비슷한 일을 하는 함수들이 사소한 차이로 개별적으로 존재하는 경우

      👉 추상화 를 통해 하나의 함수/ 모듈/ 클래스를 사용하여 사소한 차이점을 (argument 등으로) 다루기 : 한 곳만 수정해도 다른 코드에 반영할 수 있음

      e.g. JAVA의 class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    // bad
    function showDeveloperList(developers) {
    developers.forEach(developers => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink(); // 👈 difference
    const data = {
    expectedSalary,
    experience,
    githubLink
    };

    render(data);
    });
    }

    function showManagerList(managers) {
    managers.forEach(manager => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects(); // 👈 difference
    const data = {
    expectedSalary,
    experience,
    portfolio
    };

    render(data);
    });
    }


    // good
    function showEmployeeList(employees) {
    employees.forEach((employee) => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    let portfolio = employee.getGithubLink();

    if (employee.type === 'manager') {
    portfolio = employee.getMBAProjects();
    }

    const data = {
    expectedSalary,
    experience,
    portfolio
    };

    render(data);
    });
    }
  2. Object.assign 으로 default object 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// bad
const menuConfig = {
title: null,
body: "Bar",
buttonText: null,
cancellable: true
};

function createMenu(config) {
config.title = config.title || "Foo";
config.body = config.body || "Bar";
config.buttonText = config.buttonText || "Baz";
config.cancellable =
config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);


// good
const menuConfig = {
// minimize null value and set default values to inherit them
title: "Order",
// User did not include 'body' key
buttonText: "Send",
cancellable: true
};

function createMenu(config) {
let finalConfig = Object.assign(
{
title: "Foo",
body: "Bar",
buttonText: "Baz",
cancellable: true
},
config
);
return finalConfig

// follow the order so...
// config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
}

createMenu(menuConfig);
  1. function parameter에 flags 전달하지 않기
    • flags 는 그 자체로 하나 이상의 기능을 하는 것을 의미하므로, 단일 기능을 하는 함수로 나누기!

플래그(flags)

정규 표현식을 생성할 때 플래그를 사용하여 기본 검색 설정을 변경할 수 있습니다.

이렇게 설정된 플래그는 나중에 추가되거나 삭제될 수 없습니다.

플래그(flag) 설명
i 검색 패턴을 비교할 때 대소문자를 구분하지 않도록 설정함.
g 검색 패턴을 비교할 때 일치하는 모든 부분을 선택하도록 설정함.
m 검색 패턴을 비교할 때 여러 줄의 입력 문자열을 그 상태 그대로 여러 줄로 비교하도록 설정함.
y 대상 문자열의 현재 위치부터 비교를 시작하도록 설정함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// bad
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}


// good
// temp(flags)를 기능별로 개별적인 함수로 나눔
function createFile(name) {
fs.create(name);
}

function createTempFile(name) {
createFile(`./temp/${name}`);
}
  1. Side Effect

    함수가 take a value in and return another value / values 외의 action을 할 경우 side effect가 발생함

    e.g. writing to a file, modifying some global variable, etc.

    • side effect를 발생시키는 함수를 정의할 경우, centralize where you are doing this (하나의 함수로 통일할 것!)
      • pitfall (uncatchable error) 을 피하기 위함
      • e.g. sharing state between objects without any elucidation / saving, using mutable data types that can be written to anything 👉 TypeScript 이용하기, not centralizing where your side effects occur
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // bad
    // Global variable referenced by following function.
    // If we had another function that used this name, now it'd be an array and it could break the logic it originally had.
    let name = "Ryan McDermott";

    function splitIntoFirstAndLastName() {
    name = name.split(" ");
    }

    splitIntoFirstAndLastName();

    console.log(name); // ['Ryan', 'McDermott'];


    // good
    function splitIntoFirstAndLastName(name) {
    return name.split(" ");
    }

    const name = "Ryan McDermott";
    const newName = splitIntoFirstAndLastName(name);

    console.log(name); // 'Ryan McDermott';
    console.log(newName); // ['Ryan', 'McDermott'];
    • Handling changeable (mutable) values / data by cloning
      • input object를 직접적으로 mutate하는 경우는 많지 않으므로, clone - edit - return 하기
      • improve performance efficiency using great libraries
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // bad
    const addItemToCart = (cart, item) => {
    cart.push({ item, date: Date.now() });
    };


    // good
    const addItemToCart = (cart, item) => {
    // new array
    return [...cart, { item, date : Date.now() }];
    }
  2. global function 사용하지 않기

    • 내장된 library와 충돌할 수 있음
      • 똑같은 이름의 함수가 다른 action
      • 다른 이름의 함수가 같은 action
    • error handling, exception 처리 없이는 해당 API를 사용할 때 문제 발생

    👉 inheritance 이용하기!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
};


// good
// inheritance
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem => !hash.has(elem));
}
}
  1. imperative 보다 favor(functional) programming 하기

    Imperative programming : writing your program as a series of instructions (statements) that can actively modify memory (variables, arrays)

    • focuses on how : express the logic of your program based on how the computer would execute it

    Functional programming : writing your program in terms of functions and other mathematical structures

    • A function is just a rule that maps input elements to output
    • concentrates on the what : trying to let you specify the logic of your program as close to actual logic as possible
    • more expressive : writing less code
    • safer : more bugs are prevented by the language ahead of time
    • side-effects are controlled
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const programmerOutput = [
{
name: 'Uncle Bobby',
linesOfCode: 500
}, {
name: 'Suzie Q',
linesOfCode: 1500
}, {
name: 'Jimmy Gosling',
linesOfCode: 150
}, {
name: 'Gracie Hopper',
linesOfCode: 1000
}
];


// bad
let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {
totalOutput += programmerOutput[i].linesOfCode;
}


// good
const totalOutput = programmerOutput
.map(programmer => programmer.linesOfCode)
.reduce((acc, linesOfCode) => acc + linesOfCode, INITIAL_VALUE);
  1. 조건문(Conditionals) Encapsulation

Encapsulation

bundling data and methods that work on that data within one unit

  • hide the internal representation, or state, of an object from the outside
  • e.g., a class in Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad
if (fsm.state === 'fetching' && isEmpty(listNode)) {
// ...
}


// good
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
  1. 부정조건문 피하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bad
function isDOMNodeNotPresent(node) {
// ...
}

if (!isDOMNodeNotPresent(node)) {
// ...
}


// good
function isDOMNodePresent(node) {
// ...
}

if (isDOMNodePresent(node)) {
// ...
}
  1. 조건문 피하기
  • 조건문 자체가 함수가 하나 이상의 일을 하고 있음을 의미함

👉 각 case에 해당되는 함수 개별적으로 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// bad
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}


// good
class Airplane {
// ...
}

class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}

class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}

class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
  1. Type-checking 피하기
  • consistent APIs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // bad
    function travelToTexas(vehicle) {
    if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location('texas'));
    } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location('texas'));
    }
    }


    // good
    // case 별로 move function을 달리 하는 것이 아니라,
    // `move`로 동일하게 생성
    function travelToTexas(vehicle) {
    vehicle.move(this.currentLocation, new Location('texas'));
    }
  • TypeScript

    • basic primitive를 이용해서 polymorphism 속성을 활용할 수 없을 때
    • provides you with static typing on top of standard JavaScript syntax
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // bad
    function combine(val1, val2) {
    if (typeof val1 === 'number' && typeof val2 === 'number' ||
    typeof val1 === 'string' && typeof val2 === 'string') {
    return val1 + val2;
    }

    throw new Error('Must be of type String or Number');
    }


    // good
    // ※ with Typescript
    function combine(val1, val2) {
    return val1 + val2;
    }
  1. over-optimising 피하기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // bad
    // On old browsers, each iteration with "uncached" `list.length` would be costly
    // (need to recomputate every time it runs for loop)
    // because of `list.length` recomputation. In modern browsers, this is optimized.
    for (let i = 0, len = list.length; i < len; i++) {
    // ...
    }


    // good
    for (let i = 0; i < list.length; i++) {
    // ...
    }
  2. Dead Code 지우기

    • If it’s not being called, get rid of it!
      • keep it in your version history if you still need it
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // bad
    function oldRequestModule(url) {
    // ...
    }

    function newRequestModule(url) {
    // ...
    }

    const req = newRequestModule;
    inventoryTracker('apples', req, 'www.inventory-awesome.io');


    // good
    function newRequestModule(url) {
    // ...
    }

    const req = newRequestModule;
    inventoryTracker('apples', req, 'www.inventory-awesome.io');