Skip to content

함수

함수 인자(가급적 2개 이하)

함수 매개변수 개수 제한은 테스트 용이성을 위해 매우 중요합니다. 3개 이상의 매개변수가 존재할 경우 각 인자의 조합적 폭발이 발생하여 다양한 테스트 케이스를 모두 검증해야 합니다.

1-2개의 인자가 이상적이며 3개 이상은 가능한 피해야 합니다. 더 많은 매개변수가 필요할 경우 일반적으로 상위 레벨 객체를 인자로 전달하는 것이 효과적입니다. 이는 함수가 여러 책임을 지고 있음을 의미할 수 있습니다.

JavaScript의 동적 객체 생성 기능을 활용하면 다수 인자 대신 단일 객체를 사용할 수 있습니다.

ES2015/ES6 구조 분해 할당 문법을 사용하면 함수가 기대하는 속성을 명확히 할 수 있습니다. 이 접근법의 장점은 다음과 같습니다:

  1. 함수 시그니처를 확인하는 즉시 사용되는 속성을 파악 가능
  2. 명명된 매개변수(named parameters) 시뮬레이션 가능
  3. 구조 분해는 인자 객체의 기본형 값을 복제하므로 부수 효과 방지에 도움(※ 객체/배열은 복제되지 않음)
  4. 사용되지 않은 속성에 대한 린터 경고 활성화 가능

나쁜 예:

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

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

좋은 예:

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

createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});

단일 책임 원칙 준수

이 규칙은 소프트웨어 공학에서 가장 중요한 원칙입니다. 다중 기능을 수행하는 함수는 조합, 테스트, 이해가 어려워지며, 단일 액션으로 격리된 함수는 리팩토링과 가독성이 우수합니다. 본 가이드 중 이 원칙만 적용해도 개발 역량이 향상됩니다.

나쁜 예:

javascript
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

좋은 예:

javascript
function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

함수명은 기능을 명시해야 함

나쁜 예:

javascript
function addToDate(date, month) {
  // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
addToDate(date, 1);

좋은 예:

javascript
function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

단일 추상화 수준 유지

다중 추상화 수준은 함수의 과도한 복잡성을 나타냅니다. 함수 분할을 통해 재사용성과 테스트 용이성을 확보하세요.

나쁜 예:

javascript
function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

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

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

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

좋은 예:

javascript
function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}

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

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

  return tokens;
}

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

  return syntaxTree;
}

중복 코드 제거

중복 코드는 로직 변경 시 다수 위치를 수정해야 하므로 철저히 방지해야 합니다.

레스토랑 재고 관리 사례로 설명: 토마토 재고를 여러 목록에서 관리하면 모든 목록을 업데이트해야 하지만, 단일 목록 사용 시 한 번만 수정하면 됩니다.

유사하지만 약간 다른 기능들로 인한 중복 코드 발생 시 추상화를 통해 통합 구현해야 합니다. SOLID 원칙에 기반한 적절한 추상화 설계가 핵심입니다.

잘못된 추상화는 중복 코드보다 해로울 수 있으므로 주의해야 합니다. 효과적인 추상화 구현 시 변경 관리가 용이해집니다.

나쁜 예:

javascript
function showDeveloperList(developers) {
  developers.forEach(developer => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    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();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}

좋은 예:

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

    const data = {
      expectedSalary,
      experience
    };

    switch (employee.type) {
      case "manager":
        data.portfolio = employee.getMBAProjects();
        break;
      case "developer":
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}

Object.assign으로 기본 객체 설정

나쁜 예:

javascript
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);

좋은 예:

javascript
const menuConfig = {
  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
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

플래그 매개변수 사용 금지

플래그는 함수가 다중 기능을 수행함을 의미합니다. 부울 값에 따른 분기 처리가 필요한 경우 함수를 분리해야 합니다.

나쁜 예:

javascript
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

좋은 예:

javascript
function createFile(name) {
  fs.create(name);
}

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

부수 효과 방지 (1부)

부수 효과는 파일 기록/전역 변수 변경 등 입출력 외 시스템 상태 변경을 의미합니다. 필요한 경우 서비스 계층에서 중앙 집중 관리해야 합니다.

공유 상태/가변 데이터 타입 사용을 피하고 부수 효과 발생 지점을 통제해야 합니다. 체계적인 관리가 가능하면 유지보수성이 크게 향상됩니다.

나쁜 예:

좋은 예:

javascript
// 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 it.
let name = "Ryan McDermott";

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

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];
javascript
function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}

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

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

부수 효과 방지 (2부)

JavaScript 객체/배열은 변경 가능하므로 함수 매개변수로 전달 시 주의가 필요합니다. 예시 쇼핑 카트 수정 시 의도치 않은 부수 효과 발생 가능성을 설명합니다.

네트워크 요청 중 사용자가 실수로 상품 추가 시 예상치 못한 데이터 전송 문제 발생 사례를 제시합니다.

클론 생성 후 수정 반영 방식으로 해결책 제시: addItemToCart 함수가 cart 복제본을 반환하도록 구현합니다.

주의 사항 2가지: 입력 객체 수정이 필요한 드문 경우 고려, 대형 객체 복제 시 성능 문제(Immutable.js 등의 라이브러리 사용 권장)

나쁜 예:

좋은 예:

  1. ⬆ 상단으로

  2. 전역 함수 수정 금지

전역 공간 오염은 라이브러리 충돌 위험을 초래합니다. Array.prototype 확장 대신 ES6 클래스 상속을 사용해야 합니다.

javascript
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

나쁜 예:

javascript
const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};

좋은 예:

⬆ 상단으로

명령형보다 함수형 프로그래밍 지향

JavaScript는 함수형 패러다임을 지원하므로 더 깔끔하고 테스트 가능한 코드 작성을 위해 함수형 스타일을 권장합니다.

javascript
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

나쁜 예:

javascript
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

좋은 예:

⬆ 상단으로

조건문 캡슐화

나쁜 예:

javascript
const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

let totalOutput = 0;

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

좋은 예:

javascript
const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0
);

부정 조건문 회피

나쁜 예:

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

좋은 예:

javascript
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

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

조건문 최소화

다형성을 활용해 조건문을 대체할 수 있습니다. 함수 단일 책임 원칙을 준수하면 if 문 사용을 크게 줄일 수 있습니다.

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

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

나쁜 예:

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

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

좋은 예:

⬆ 상단으로

타입 체크 회피 (1부)

JavaScript의 동적 타이핑 특성상 타입 체크를 유혹받을 수 있지만 일관된 API 설계로 이를 방지해야 합니다.

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

나쁜 예:

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

좋은 예:

⬆ 상단으로

타입 체크 회피 (2부)

기본형 값 다룰 때 TypeScript 사용을 고려해야 합니다. 수동 타입 체크는 가독성을 저해하므로 깨끗한 코드/테스트/리뷰 프로세스를 유지해야 합니다.

javascript
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"));
  }
}

나쁜 예:

javascript
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}

좋은 예:

⬆ 상단으로

과도한 최적화 방지

현대 브라우저는 자체 최적화 기능을 보유하므로 최적화 병목 지점 분석 자료를 참고해 목표를 명확히 해야 합니다.

javascript
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");
}

나쁜 예:

javascript
function combine(val1, val2) {
  return val1 + val2;
}

좋은 예:

⬆ 상단으로

사용되지 않는 코드 제거

데드 코드는 중복 코드와 동일한 문제를 유발합니다. 버전 관리 시스템에 기록이 남으므로 즉시 삭제해야 합니다.

javascript
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

나쁜 예:

javascript
for (let i = 0; i < list.length; i++) {
  // ...
}

좋은 예:

⬆ 상단으로

$$101$$

$$102$$

javascript
function oldRequestModule(url) {
  // ...
}

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

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

$$103$$

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

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

$$104$$