읽기좋은 코드가 좋은 코드다 Part 3

dongwon·2022년 1월 5일
0

TIL

목록 보기
16/17

상관없는 하위 문제 추출하기

큰 흐름과 관계가 적은 하위 문제들을 추출하자
1. 주어진 함수나 코드를 보고 질문해라. 상위 수준에서 본 이 코드의 목적은?
2. 코드의 모든 줄에 질문해라. 이 코드는 직접적으로 목적을 위해 존재하나? 혹은 하위 문제를 해결하는 목적인가?
3. 만약 원래의 목적과 직접적으로 관련되지 않은 하위 문제를 해결하는 코드 분량이 많으면 이를 추출해 별도의 함수로 만든다.

예시 코드

var 가까운위치찾기 = function(lat, lng, array) {
    var closest;
    var closest_dist = Number.MAX_VALUE;

    for(var i = 0 ; i < array.length < i++){
        //////////////  상관없는 하위 코드  //////////////
        var lat_rad = radians(lat);
        var lng_rad = radians(lng);
        var lat2_rad = radians(array[i].latitude);
        var lng2_rad = radians(array[i].longitude);

        // 코사인 특별법칙
        var dist = Math.acos( ... );
        /////////////////////////////////////////////////


        // 상위 수준의 진짜 목적인 코드
        if(dist < closest_dist) {ss
            closest = array[i];
            closest_dist = dist;
        }
    }

    return closest;
}

상관없는 하위코드들은 다음 함수로 묶으면, 상위 수준의 진짜 목적인 코드에 집중할 수 있다.

var spherical_distance = function(lat1, lng1, lat2, lng2) {
        var lat_rad = radians(lat);
        var lng_rad = radians(lng);
        var lat2_rad = radians(array[i].latitude);
        var lng2_rad = radians(array[i].longitude);

        return Math.acos( ... );
}

var 가까운위치찾기 = function(lat, lng, array) {
    var closest;
    var closest_dist = Number.MAX_VALUE;

    for(var i = 0 ; i < array.length < i++){
        var dist = spherical_distance(lat1, lng1, array[i].latitude, array[i].longitude);

        // 진짜 목적인 코드
        if(dist < closest_dist) {
            closest = array[i];
            closest_dist = dist;
        }
    }

    return closest;
}

순수한 유틸리티 코드

문자열 변경, 해시테이블, 파일 읽기/쓰기와 같이 기본적인 유틸리티는 보통 프로그래밍 언어에 내장되어 있다. 하지만 직접 구현해야 하는 경우 예를 들어 폼을 변경하는 함수는 별도로 함수로 추출되어야 하는 직접 상관없는 하위 문제를 다루는 함수의 대표적인 예다.

일반적인 목적의 모음

예를 들어 console.log()alert 함수와 같이 목적과 직접 상관없는 하위 문제들은 코드를 추출해서 함수로 만들어라. (이 경우 이러한 함수들로만 이루어진 여러 줄의 코드인 경우)

var format_pretty = function (obj) {
    var str = "{\n";
    for ( var key in obj ) {
        str += " " + key + " = " + obj[key] + "\n";
    }
    return str + "}";
}

뜻하지 않는 장점들

위의 format_pretty 함수를 따로 분리하면서 얻는 장점이 많다. 먼저 코드가 깔끔해지고 다른 곳에서도 간편하게 사용할 수 있다.
이러한 장점들 외에도 더 눈에띄는 장점이라면 성능개선이다. 이 함수는 예외설정을 하지 않았는데, 따로 분리한다면 기능 설정, 가독성 개선, 엣지케이스를 다루는 것이 상대적으로 쉽게 된다.

var format_pretty = function (obj) {
    // 함수를 따로 분리한다면 obj가 null인 예외를 손쉽게 처리 가능
    if(obj === null) return null; 

    return ...
}

일반적인 목적을 가진 코드를 많이 만들어라

위와 같은 경우는 상관없는 하위 문제를 다루는 대표적인 함수다. 이러한 함수는 보통 utils 디렉토리에 담겨져 코드를 쉽게 공유할 수 있다. 이러한 코드는 개발하고 테스트하고 이해하기도 쉽다.

특정한 프로젝트를 위한 기능

이상적인 상황이라면 추출한 하위 문제는 사용하는 프로젝트를 전혀 몰라야 하지만 그렇지 않더라도 큰 상관은 없다. 분리하는 것만으로도 큰 도움이 되기 때문이다.

기존의 인터페이스를 단순화하기

라이브러리가 깔끔한 인터페이스를 제공하자. 적은 수의 인수를 받고 별다른 설정을 요구하지 않고 사용하기 간편한 인터페이스가 좋다.

한 번에 하나씩

함수는 오직 한 가지 작업만 수행해야 한다라는 관점으로 코드를 본다. 이런 관점을 수행하게 하는 관점은 다음과 같다.
1. 코드가 수행하는 모든 작업을 나열한다.
2. 이러한 작업들을 분리하여 서로 다른 함수로 혹은 적어도 논리적으로 구분되는 영역에 놓을 수 있는 코드로 만들어라.

작업은 작을 수 있다.

추천, 반대는 한 번씩 더 누르면 취소되는 추천/반대/점수 합산하는 자바스크립트 코드가 있다.

var vote_changed = function(old_vote, new_vote){
    var score = get_score();

    if(new_vote !== old_vote){
        if(new_vote === 'up'){
            score += (old_vote === 'down' ? 2: 1);
        }else if(new_vote === 'down'){
            score -= (old_vote === 'up' ? 2: 1);
        }else if(new_vote === ''){
            score += (old_vote === 'up' ? -1: 1);
        }
    }

    set_score(score);
}

이 코드는 한 번에 2가지 일을 하고 있다.
1. up, down이 수치값으로 해석된다.
2. 점수가 변경된다. ( set_score )

수치값으로 해석되는 것을 하나의 기능을 하는 함수로 묶으면, 다음과 같이 나타낼 수 있다.

var vote_value = function(vote){
    if(vote === 'up'){ return +1 }
    if(vote === 'down'){ return -1 }
    return 0;
}

하나로 합치면,

var vote_changed = function(old_vote, new_vote){
    var score = get_score();

    score += vote_value(old_vote); // 이전 값 제거
    score -= vote_value(new_vote); // 새 값 추가

    set_score(score);
}

생각을 코드로 만들기

  1. 코드가 할 일을 옆의 동료에게 말하듯 평범한 영어로 묘사해라
  2. 이 설명에 들어가는 핵심적인 단어와 문구를 포착해라
  3. 설명과 부합하는 코드를 작성해라

논리를 명확하게 설명하기

특정 문서에 입장을 허가하는 코드가 있다. 이 페이지에 입장하라면,
1. 관리자다.
2. 문서가 존재하고, 그 문서가 내 문서다

이 논리를 코드로 작성했을 때, 다음과 같은 코드면 굉장히 어지럽다.

if(문서 존재){
    if(!관리자 && 문서의 주인 !==){
        return 입장 불가;
    }
} else{
    if(!관리자){
        return 입장 불가
    }
}....

다음과 같이 바꾸자.

if(관리자){
    return 입장
}else if(문서 존재 && 문서의 주인 ===){
    return 입장
}else{
    return 입장 불가
}

코드 분량 줄이기

요구사항에 질문을 던지고 질문을 잘게 나누어 분석해라

주어진 요구사항을 잘 분석해 보면 적은 코드로도 문제를 구현할 수 있다. 예를 들어 사용자의 위도, 경도를 이용해 가까운 상점을 찾는 코드를 작성한다고 해보자. 고려해야 할 것은 다음과 같다.
1. 장소가 날짜 변경선의 어느 한쪽에 있다.
2. 북극이나, 남극에 가깝다.
3. 1마일마다 경도의 값이 달라지므로 곡률을 계산한다.

이것은 지구 전체에 있는 상점들을 찾는다라고 가정했을 때 고려해야 할 것들이다. 하지만 범위를 한국으로 축소한다면 위와 같은 것들은 고려할 필요가 없어 전보다 적은 코드로 문제를 구현할 수 있다.

코드베이스 작게 유지하기

  1. 일반적인 유틸리티를 많이 생성해 중복 코드를 없애자.
  2. 사용하지 않는 코드, 필요 없는 기능을 제거해라.
  3. 프로젝트가 서로 분절된 하위 프로젝트로 구성되게 해라.
  4. 코드베이스의 무게를 항상 의식하여 가볍고 날렵하게 유지해라.

라이브러리에 친숙해져라

프로그래머는 이미 존재하는 라이브러리를 직접 구현하고 있다. 모든 기능을 외우는 게 아니라 이 기능을 기억하고 필요할 때 찾아서 쓸 수 있는 것이 중요하다.

profile
데이원컴퍼니 프론트엔드 개발자입니다.

0개의 댓글