프론트엔드/JavaScript

[JavaScript] 클린 코드 - 3. 함수 (2)

1. 함수형 프로그래밍을 지향한다.

(여기선 간단히만 다룸. 중요한 패러다임이니 따로 공부할 예정)

 

함수형 프로그래밍이란 함수를 기반으로 하는 프로그래밍이다.

이러한 함수형 프로그래밍에선 순수 함수를 사용해야 한다.

  • 순수 함수
    • 같은 입력이 주어지면 항상 같은 값을 반환해야 한다.
    • 부작용(Side effect)이 없어야 한다.
      = 함수의 실행이 프로그램의 실행에 영향을 미치지 않아야 한다.
      ex 함수 내부에서 인자의 값을 변경하거나 프로그램 상태를 변경하는 행위

프로그램에 변화를 주지 않고, 입력에 대한 결과를 예측할 수 있어서 테스트가 쉬워진다.

 

또한 함수형 프로그래밍은 데이터를 변경하지 않고 기존 데이터의 복사본을 다뤄야 한다.

복사본을 만들기 위한 JavaScript의 대표적인 순수 함수는 map, filter, reduce가 있다.

// BAD - 반환값 없음. 외부 인자의 값 변경
function addPerson(array) {
  array.push("John");
}

// BAD - 인자 대신 외부 변수를 사용
function addPerson() {
  return [...globalArray, "John"];
}

// GOOD! - 순수 함수
function addPerson(array) {
  return [...array, "John"];
}

 

2. 조건문은 캡슐화한다.

캡슐화를 하면 조건문의 로직을 내부로 숨기면서 캡슐화의 장점을 적용시킬 수 있다.

또한 내부 로직을 안 보고도 함수명을 통해 어느 정도 예측할 수 있는 장점이 있다.

// BAD
if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

// GOOD! - 조건문을 캡슐화
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

 

3. 부정 조건문을 사용하지 않는다.

// BAD
function isDOMNodeNotPresent(node) {
  // ...
}

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

// GOOD!
function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

 

4. 조건문 작성을 피한다.

우리는 조건문이 익숙하기 때문에 조건문 없이 코드를 짜라는 말에 의구심이 들기도 한다.

하지만 앞 게시물에 '함수는 하나의 행동만 해야 한다.'라는 중요한 원칙이 있었다.

함수에 조건문이 쓰였다면 이는 함수가 한 가지 이상의 기능을 하고 있다는 뜻이다.

조건문 대신 다형성을 이용하면 원칙을 지키면서도 동일한 기능의 함수를 만들 수 있다.

// 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();
  }
}

 

5. 타입 체킹을 도와주는 여러 방법을 사용한다.

JavaScript는 동적 타입 언어이기 때문에 타입에 있어 자유롭다는 특징이 있다.

하지만 이러한 자유로움 때문에 생기는 여러 버그가 우리의 머리를 아프게 한다..

 

이때 typeof, getType() 등의 내부 메서드를 이용해 타입을 체크하는 것보다

아래에 추천하는 좋은 방법들로 타입 체킹 문제를 해결하길 추천한다.

 

5-1. 일관성 있는 API를 사용한다.

// 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! - move로 일관성 있게
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}

 

5-2. TypeScript를 사용한다.

TypeScript는 JavaScript의 슈퍼 셋(상위 개념) 언어이다.

즉 JS의 모든 문법을 포함하면서도 interface 등의 문법을 추가로 지원한다.

 

만약 지금 타입 체킹을 해결하느라 복잡해진 코드와 많은 주석 때문에 스트레스 받는다면

TypeScript를 사용할 좋은 타이밍이다!

// BAD
function add(a, b) {
  if (typeof a === "number" && typeof b === "number") {
    return a + b;
  }
}

// GOOD! - TypeScript
function add(a: number, b: number) {
  return a + b;
}

 

6. 과도한 최적화를 피한다.

최신 브라우저는 런타임 과정에서 많은 최적화 작업을 대신 수행해준다.

오히려 내가 코드를 직접 최적화하는 것이 시간 낭비가 될 수 있다.

// 과거엔 `list.length`를 매번 계산했기 때문에 불필요한 자원 소모가 있었다.
// 하지만 최신 브라우저에서는 캐싱을 통해 이러한 연산을 최적화 한다.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

 

7. 죽은 코드는 지운다.

더 이상 호출되지 않는 죽은 코드가 있다면 지우자. 죽은 코드는 중복된 코드만큼 좋지 않다.

언젠가 또 쓰일까봐 함부로 못 지우겠다면 GIT 등의 버전 관리 툴을 이용하자.

그 코드는 내 레퍼지토리에 남아 있으니 안심하고 지울 수 있을 것이다. 

// BAD - oldRequestModule는 안쓰임
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");

 

참조