코드 세척: 순환 피하기


당신은 내가 곧 출판할 깨끗한 코드에 관한 책의 발췌문을 읽고 있습니다. "당신의 코드를 씻으세요: 한 번 쓰고 일곱 번 읽으세요."Preorder it on Leanpub 또는 read a draft online.
전통적인 순환, 예를 들어 for 또는 while은 흔히 볼 수 있는 임무에 있어서 등급이 너무 낮다.그것들은 매우 지루해서 off-by-one error이 나타나기 쉽다.너는 반드시 스스로 색인 변수를 관리해야 한다. 나는 항상 lenght으로 타자를 친다.그것들은 특정한 의미 가치가 없다. 당신이 특정한 조작을 한 번에 실행할 수 있는 것을 제외하고는.

수조 방법으로 순환을 바꾸다


현대 언어는 교체 연산을 표현하는 더 좋은 방법이 있다.JavaScript has may useful methods 변환과 교체수 그룹, 예를 들어 .map() 또는 .find().
예를 들어 kebab-case 순환을 사용하여 문자열 그룹을 for으로 변환합니다.
const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
for (let i = 0; i < names.length; i++) {
  names[i] = _.kebabCase(names[i]);
}
현재 .map() 메서드 사용:
const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
const kebabNames = names.map(name => _.kebabCase(name));
만약 우리의 처리 함수가 하나의 매개 변수만 받아들이고 kebabCase from Lodash이 하나의 매개 변수를 받아들인다면 우리는 그것을 더욱 단축할 수 있다.
const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
const kebabNames = names.map(_.kebabCase);
그러나 이것은 확장 버전의 가독성보다 약간 떨어질 수 있다. 왜냐하면 우리는 함수에 전달되는 정확한 내용을 볼 수 없기 때문이다.이전의 익명 함수 문법에 비해 ECMAScript 6의 arrow 함수는 리셋을 더욱 짧고 복잡하지 않게 합니다.
const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
const kebabNames = names.map(function(name) {
  return _.kebabCase(name);
});
또는 for의 순환을 가진 그룹에서 원소를 찾습니다.
const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
let foundName;
for (let i = 0; i < names.length; i++) {
  if (names[i].startsWith('B')) {
    foundName = names[i];
    break;
  }
}
현재 .find() 메서드 사용:
const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
const foundName = names.find(name => name.startsWith('B'));
이 두 가지 상황에서 나는 for순환보다 수조 방법의 버전을 더 좋아한다.그것들이 더 짧아서 우리는 교체 메커니즘에서 코드의 절반을 낭비하지 않을 것이다.

수조 방법의 은밀한 의미


수조 방법은 더욱 짧고 읽을 수 있을 뿐만 아니라 더욱 강하다.모든 방법에는 명확한 의미가 있다.
  • .map()은 우리가 한 수조를 같은 원소수를 가진 다른 수조로 바꾸고 있음을 나타낸다.
  • .find()은 우리가 한 수조에서 원소를 찾았다는 것을 나타낸다.
  • .some()은 우리가 일부 수조 원소의 조건이 성립되었는지 테스트하고 있음을 나타낸다.
  • .every()은 모든 수조 원소의 조건이 정확한지 테스트하고 있음을 나타낸다.
  • 전체 내용을 읽기 전에 전통적인 순환은 코드가 무엇을 하는지 이해하는 데 도움이 되지 않는다.
    우리는'무엇'(우리의 데이터) 과'어떻게'(어떻게 순환하는지) 를 분리할 것이다.더 중요한 것은 수조 방법에 대해 우리는 데이터만 걱정하고 이를 리셋 함수로 전달한다.
    모든 간단한 상황에 대해 수조 방법을 사용할 때, 전통적인 순환은 코드 리더에게 신호를 보내서 이상한 상황이 발생했음을 나타낸다.이것은 매우 좋다. 심상치 않고 복잡한 상황을 더 잘 이해하기 위해 뇌 자원을 보존할 수 있다.
    또한 더욱 전문적인 수조 방법이 실행될 때 .map() 또는 .forEach() 등 일반적인 수조 방법을 사용하지 말고 .forEach()이 실행될 때 .map()을 사용하지 마십시오.
    const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
    const kebabNames = [];
    names.forEach(name => {
      kebabNames.push(_.kebabCase(name));
    });
    
    이것은 .map()의 더욱 신비롭고 의미가 적은 실현이기 때문에 위와 같이 .map()을 직접 사용한다.
    const names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
    const kebabNames = names.map(name => _.kebabCase(name));
    
    이 판본은 .map() 방법이 같은 항수를 유지함으로써 그룹을 바꾸는 것을 알고 있기 때문에 더욱 쉽게 읽을 수 있다..forEach()과 달리 사용자 정의 구현도 필요 없고 출력 그룹을 수정할 필요도 없습니다.그 밖에 리셋 함수는 현재 순수 함수입니다. 부모 함수의 어떤 변수에도 접근하지 않고 함수 매개 변수에만 접근합니다.

    부작용 처리


    부작용은 코드를 더욱 이해하기 어렵다. 함수를 블랙박스로 볼 수 없기 때문이다. 부작용이 있는 함수는 입력을 출력으로 전환할 뿐만 아니라 예측할 수 없는 방식으로 환경에 영향을 미칠 수 있다.부작용이 있는 함수도 테스트하기 어렵다. 매번 테스트하기 전에 환경을 다시 만들고 검증해야 하기 때문이다.
    이전 절에서 언급한 모든 수조 방법(.forEach() 제외)은 부작용이 없고 되돌아오는 값만 사용한다는 것을 의미한다.이런 방법에 어떤 부작용을 도입하더라도 코드를 오독하기 쉽다. 왜냐하면 독자들이 부작용을 보고 싶지 않기 때문이다..forEach()은 어떤 값도 반환하지 않습니다. 부작용이 진정으로 필요할 때 부작용을 처리하는 올바른 선택입니다.
    errors.forEach(error => {
      console.error(error);
    });
    
    for of 더 좋은 순환:
  • 은 본 장에서 처음에 언급한 일반적인 for순환의 어떤 문제도 없다.
  • 우리는 재분배와 돌연변이를 피할 수 있다. 왜냐하면 우리는 반환치가 없기 때문이다.
  • 은 모든 수조 원소에 명확한 교체 의미를 가진다. 왜냐하면 우리는 일반적인 for 순환에서처럼 교체 횟수를 조종할 수 없기 때문이다.(좋아, 거의 break으로 순환을 중지할 수 있어.)
  • for of 루프를 사용하여 예제를 다시 작성합니다.
    for (const error of errors) {
      console.error(error);
    }
    

    때로는 순환이 그렇게 나쁘지 않다


    수조 방법이 항상 순환보다 우수한 것은 아니다.예를 들어 .reduce() 방법은 일반적으로 코드의 가독성을 일반적인 순환보다 낮게 한다.
    이 코드를 살펴보겠습니다.
    const tableData = [];
    if (props.item && props.item.details) {
      for (const client of props.item.details.clients) {
        for (const config of client.errorConfigurations) {
          tableData.push({
            errorMessage: config.error.message,
            errorLevel: config.error.level,
            usedIn: client.client.name
          });
        }
      }
    }
    
    내 첫 번째 반응은 순환을 피하기 위해 .reduce()으로 다시 쓰는 것이다.
    const tableData =
      props.item &&
      props.item.details &&
      props.item.details.clients.reduce(
        (acc, client) => [
          ...acc,
          ...client.errorConfigurations.reduce(
            (inner, config) => [
              ...inner,
              {
                errorMessage: config.error.message,
                errorLevel: config.error.level,
                usedIn: client.client.name
              }
            ],
            []
          )
        ],
        []
      );
    
    근데 진짜 더 읽을 수 있을까요?
    커피 한 잔을 마시고 동료와 이야기를 나눈 후에 나는 더욱 간결한 코드를 얻었다.
    const tableData =
      props.item &&
      props.item.details &&
      props.item.details.clients.reduce((acc, client) =>
        acc.concat(
          ...client.errorConfigurations.map(config => ({
            errorMessage: config.error.message,
            errorLevel: config.error.level,
            usedIn: client.client.name
          }))
        ),
        []
      );
    
    나는 여전히 쌍for 버전을 더 좋아한다고 생각한다. 그러나 만약 내가 이런 코드를 복습해야 한다면, 나는 두 버전, 원시 버전과 두 번째 다시 쓰는 버전을 보게 되어 매우 기쁠 것이다.
    (비록 tableData은 매우 나쁜 변수 이름이지만.)

    객체에서 교체


    JavaScript에는 many ways to iterate over objects이 있습니다.나 역시 그들을 좋아하지 않기 때문에 가장 좋은 것을 선택하기가 매우 어렵다.불행하게도 대상은 .map()이 없습니다. 비록 Lodash는 세 가지 대상을 교체하는 방법이 있지만, 프로젝트에서 Lodash를 사용했다면 이것은 좋은 선택입니다.
    const allNames = {
      hobbits: ['Bilbo', 'Frodo'],
      dwarfs: ['Fili', 'Kili']
    };
    const kebabNames = _.mapValues(allNames, names =>
      names.map(name => _.kebabCase(name))
    );
    
    만약 결과를 대상으로 삼을 필요가 없다면, 상기에서 보듯이 Object.keys(), Object.values()Object.entries()도 좋다.
    const allNames = {
      hobbits: ['Bilbo', 'Frodo'],
      dwarfs: ['Fili', 'Kili']
    };
    Object.keys(allNames).forEach(race =>
      console.log(race, '->', allNames[race])
    );
    
    또는:
    const allNames = {
      hobbits: ['Bilbo', 'Frodo'],
      dwarfs: ['Fili', 'Kili']
    };
    Object.entries(allNames).forEach(([race, value]) =>
      console.log(race, '->', names)
    );
    
    나는 그들에 대해 강렬한 선호가 없다.Object.entries()은 더 상세한 문법을 가지고 있지만, 이 값을 여러 번 사용한다면, 코드는 names보다 간결할 것입니다. Object.keys()에서는 allNames[race]을 쓸 때마다 쓰거나 리셋 함수의 시작 부분에서 변수에 캐시해야 합니다.
    만약 내가 여기에 멈추면, 나는 너에게 거짓말을 할 것이다.대부분의 대상 교체에 관한 글은 console.log()의 예가 있지만, 실제로, 당신은 보통 하나의 대상을 다른 데이터 구조로 바꾸기를 원합니다. 위의 _.mapValues()의 예와 같습니다.이것이 바로 일이 더욱 나빠지기 시작한 곳이다.
    예를 .reduce()으로 다시 작성해 보겠습니다.
    const kebabNames = Object.entries(allNames).reduce(
      (newNames, [race, names]) => {
        newNames[race] = names.map(name => _.kebabCase(name));
        return newNames;
      },
      {}
    );
    
    .forEach():
    const allNames = {
      hobbits: ['Bilbo', 'Frodo'],
      dwarfs: ['Fili', 'Kili']
    };
    const kebabNames = {};
    Object.entries(allNames).forEach(([race, names]) => {
      kebabNames[race] = names.map(name => name.toLowerCase());
    });
    
    또 하나의 순환:
    const kebabNames = {};
    for (let [race, names] of Object.entries(allNames)) {
      kebabNames[race] = names.map(name => name.toLowerCase());
    }
    
    마찬가지로 .reduce()은 가독성이 가장 낮은 옵션입니다.
    뒷부분에서, 나는 당신에게 순환을 피해야 할 뿐만 아니라, 변수와 변이를 재분배하는 것도 피해야 한다고 촉구할 것입니다.순환과 마찬가지로, 그것들은 통상적으로 코드의 가독성을 떨어뜨릴 수 있지만, 때로는 그것들이 가장 좋은 선택이다.

    근데 수조 방법이 느리지 않아요?


    순환보다 함수 사용이 느리다고 생각할 수도 있고, 그럴 수도 있습니다.하지만 수백만 개의 물건을 처리하고 있지 않으면 사실상 중요하지 않다.
    현대 자바스크립트 엔진은 속도가 매우 빠르고 유행하는 코드 모델을 최적화시켰다.이전에 우리는 자주 이렇게 순환을 썼다. 왜냐하면 매번 교체될 때마다 수조의 길이를 검사하는 것이 너무 느리기 때문이다.
    var names = ['Bilbo Baggins', 'Gandalf', 'Gollum'];
    for (var i = 0, namesLength = names.length; i < namesLength; i++) {
      names[i] = _.kebabCase(names[i]);
    }
    
    그것은 더 이상 느리지 않다.또 다른 몇 가지 예는 엔진 최적화는 더욱 간단한 코드 모델을 얻기 위해 수동 최적화가 필요하지 않다는 것이다.어떤 상황에서도 최적화할 내용과 코드가 모든 중요한 브라우저와 환경에서 더 빠른지 알아보기 위해 성능을 평가해야 합니다.
    그 밖에 .every(), .some(), .find().findIndex()은 단락을 이루는데 이것은 그들이 너무 많은 수조 원소를 교체하지 않는다는 것을 의미한다.
    생각하기 시작:
  • .map() 또는 .filter()과 같은 수조 방법으로 순환을 대체한다.
  • 은 기능 부작용을 피한다.
  • 피드백이 있으면 GitHub에서 open an issue을 보내거나 [email protected]으로 이메일을 보내십시오.Preorder the book on Leanpub 또는 read a draft online.

    좋은 웹페이지 즐겨찾기