상세 컨텐츠

본문 제목

깨끗한 코드도 실력이다. Clean Code (Function)

기획

by 신시웨이 2020. 11. 6. 17:11

본문

모든 코드가 그렇듯이 Javascript 코드 또한 작성하는 방식과 그 형태에 따라 유지보수와 협업에 용이하며, 리팩토링을 간편하게끔 만들 수 있습니다.

이번 칼럼에서는 함수(function)를 작성하는 방법에 대해서 정리해보도록 하겠습니다. 기본적으로 함수라 하면 많은 개발자들이 함수형 함수, 선언형 함수 등 다양한 방식에 관해 떠올리기도 합니다. 하지만 이 칼럼에서는 그 베이스가 되는 클린 코드 작성법에 대하여 정리하도록 하겠습니다.

 

1. 함수 인자는 2개 이하로 작성하자

함수에서 매개 변수의 양을 제한하는 것은 주로 쉬운 테스트를 위해 중요하다고 할 수 있습니다. 3개 이상이 된다면 개별 인수로써 많은 경우의 수를 테스트 해야 하기 때문에 다양한 실수가 발생할 수 있습니다. 만약 3개 이상이 된다면 통합화하는 것이 좋습니다. 예를 들어 json 같은 객체를 사용하여 하나의 인수로 묶는 것이 좋습니다.

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

createMenu({ 
 title       : “Foo”, 
 body      : “Bar“, 
 buttonText : ”Baz“, 
 cancellable : true 
})

위의 함수는 2개 이상의 매개 변수를 가지고 있으므로 json 데이터를 활용하여 하나의 객체로 묶는 편이 좋습니다. 기준에 따라 2개의 변수로 나누어 활용해도 됩니다.

 

2. 하나의 함수는 하나의 행동만 수행하도록 작성하자 

기능 단위로 함수를 분리하여 하나의 함수가 하나의 행동을 수행하도록 하면 단위 테스트가 쉽고 리팩토링이 수월해집니다. 또한, 코드가 깔끔해지는 장점도 있습니다. 최근에는 주석이 없는 코드를 잘 짠 코드라고 불립니다. 읽기가 쉬운 코드를 만들기 위해서는 지향해야 하는 방법 중 가장 중요한 방법일 것입니다.

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

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

3. 함수명은 무엇을 하는지를 나타내게 작성하자

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

const date = new Date();
addToDate(date, 1);
function addMonthToDate(month, date) {
  // ...
}

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

addToDate라는 함수는 직역하여 어떤 것을 date에 추가시키는지 명확하지 않습니다.

하지만 addMonthToDate로 변경함으로써 불필요한 주석을 지우고, 명확한 함수의 역할을 이해할 수 있습니다. 이처럼 복잡한 주석 없이 명확한 함수를 만들 수 있습니다.

 

4. 중복된 코드를 최소화하도록 노력하자 

중복코드를 최소화하여야 하는 이유는 명확합니다. 한 부분을 고칠 때, 여러 부분을 하나하나 찾아서 수정해야 하는 번거로움을 줄이고, 유지보수의 시간마저 줄일 수 있습니다. 공통점이 많은 코드를 하나의 함수 / 모듈 / 클래스로 추상화하여 만드는 것이 가장 좋습니다. 

하지만 올바른 추상화를 하는 것이 가장 중요하므로 주로 SOLID 원칙을 따라 작성합니다. 잘못된 추상화는 중복 코드보다 더 나쁠 수 있다는 것을 기억해야 하며 SOLID 원칙은 다음 시간에 작성하도록 하겠습니다.

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

5. flag 값을 매개 변수로 사용하지 말자 

flag 값을 매개 변수로 사용한다는 것은 해당 함수가 한 가지 이상의 기능을 한다는 것을 의미합니다. boolean 형으로 결과 값을 나눈 경우에는 함수를 분할하는 것이 좋습니다.

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

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

6. 전역 함수를 최대한 사용하지 않도록 하자 

전역 함수를 사용하는 것은 javascript에서 가장 나쁜 습관입니다. 이는 타 라이브러리와의 충돌을 야기할 수도 있고, API 사용자들에게는 문제가 발생하기 전까지 인지하지 못하는 잠재 위험을 가지고 있습니다. 따라서 재사용하여야 할 때에는 단순히 class Array를 사용하여 글로벌을 확장하는 것이 더 좋습니다.

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

이처럼 diff라는 전역 정의 함수를 재정의하지 않고, class를 생성하여 array를 확장하는 것이 나뿐만 아니라 다른 개발자들을 위해서도 좋습니다.

 

7. 전역 함수를 최대한 사용하지 않도록 하자

전역 함수를 사용하는 것은 javascript에서 가장 나쁜 습관입니다. 이는 타 라이브러리와의 충돌 가능성이 있고, API 사용자들에게는 문제가 발생하기 전까지 인지하지 못하는 잠재위험을 가지고 있습니다. 따라서 재사용하여야 할 때에는 단순히 class Array를 사용하여 확장하는 것이 더 좋습니다.

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

이처럼 diff라는 전역 정의 함수를 재정의하지 않고, class를 생성하여 array를 확장하는 것이 자신뿐만 아니라 다른 개발자들을 위해서도 좋습니다.

 

8. ! 조건문 피하자

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

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

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

9. 조건문을 캡슐화하자

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

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

10. 과도한 최적화를 피하자

최신 브라우저들은 이미 런타임에 내부적으로 많은 최적화를 수행합니다. 따라서 코드 작성자가 과도한 최적화를 진행하는 경우 오히려 많은 시간을 낭비하는 경우가 발생할 수 있습니다.

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

과거 브라우저들에서는 list.length 같은 방식을 사용했을 경우에 느려지는 현상이 발생했습니다. 하지만 최신 브라우저들은 이를 최적화하기 때문에 별도의 방식 없이 그대로 사용하여도 무관합니다.

 

11. 불필요한 코드를 지우자

불필요한 코드. , Dead Code(데드 코드)라고 불리는 이 영역은 중복 코드라고 볼 수 있습니다. 데드 코드는 직역 그대로 사용되지 않는 코드를 뜻하는데, 유지보수를 방해하고 불필요한 영역을 차지함으로써 개발자들에게 개발의 어려움을 주기도 합니다. 필요하지 않은 코드들은 일정구석 모아두었다 삭제하거나 즉시 제거해주는 것이 좋습니다.

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

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

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");
function newRequestModule(url) {
  // ...
}

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

[참고 문헌]

Robert C. Martins - Clean Code

 

글. 접근제어팀 | 장준영

기획/편집. 프리세일즈·마케팅팀 | 박병민

관련글 더보기

댓글 영역