brunch

You can make anything
by writing

C.S.Lewis

by 아리아 Mar 18. 2018

내 코드는 좋은 코드일까?

Part 1, 읽기 좋은 코드가 좋은 코드다

코드의 퀄리티는 왜 중요할까?
                           
등산 중인 코드

코드는 컴퓨터가 해야 할 일을 나열한 지침서이며, 다른 개발자가 언제든 읽고 수정할 수 있다. 

코드의 품질은 컴퓨터, 다른 개발자의 이해도와 직결된다.

일을 하다 보면 코드를 작성하는 시간만큼, 다른 사람이 작성한 코드를 읽는 데에도 시간을 많이 할애한다.

신규 서비스를 개발 중이라면, 내가 짠 코드가 지속적으로 나의 팀원들에 의해 참조되며 수정될 것이다. 운영되고 있는 서비스를 유지 보수한다면, 기작성 된 코드로 기존 로직을 파악해 새로운 기능을 개발하거나 수정해야 하기 때문에 코드 독해에 더 많은 시간을 투자할 것이다.

그렇기에 기존 코드를 빠르게 읽어내고 이해하는 것이 중요한데, 내가 읽어야 할 코드가 엉망이라 아주 많은 시간을 할애해야 한다면 어떨까?


좋은 코드는 논리가 간단하며, 가독성이 높다. 

마치 잘 선택된 단어, 잘 다듬어진 문장, 잘 구성된 흐름을 가진 책이 술술 읽히는 것처럼 코드도 마찬가지다. 다른 개발자에게 나의 논리, 서비스의 개발 방식을 이야기하고 있는 책과도 같다. 그렇기에, 코드를 잘 작성하고 변경 사항에 대해서도 좋은 코드를 유지하기 위한 노력은 아주 중요하며, 우리 모두 잘 해나가야 할 과제이다.



당신은 보험 시스템 설계 회사에 이제 막 입사했다. 

아무런 배경 지식이 없는 상태에서 아래의 코드를 보고, 메소드가 생기게 된 이유, 하는 역할과 로직을 파악해보자.

위의 코드에 대해 한눈에 파악했는가?

(그렇다면, 집중력에 박수를.. 그래도 저렇게 작성하지 말자!)


사실 위의 코드는 보험 가입자의 납입액을 계산하기 위해, 가입자 분류를 하는 메소드를 작성한 것이다.

30세 미만이면 0을, 60세 이상이고 아프다면 1을, 60세 이상이고 아프지 않다면 2를, 30세 이상 60세 미만이면 3을 리턴한다.

위의 코드를 보고서 모든 것을 파악하기에는 정보가 부족하다. 팀원들에게 물어보거나, 주석을 보고 파악할 수도 있고, 해당 메소드가 사용되는 곳을 전부 다 찾아볼 수도 있다. `get`, `set`과 같이 매우 흔한 이름을 찾아야 한다면, 이조차 수월하지 않을 수도 있다.

하지만, 코드를 처음부터 잘 작성해 둔다면 더 빠른 시간 내에 파악할 수 있으며, 불필요한 시간을 더 쏟지 않아도 된다.


아래는 위의 메소드를 재정의 한 것이다.

- line 1~4: 0~3이 어떤 것을 의미하는지 상수로 선언함으로써, if/else 조건문이 어떤 의미인지 한눈에 파악할 수 있다.

- line 6: 주석은 메소드가 어떤 일을 하는지에 대해 서술할 때 쓰는 것이 아니다. 기능이 생기게 된 배경이나 사용하는 곳 혹은 특정 서비스에만 존재하는 일반적이지 않은 기능을 만들어야 할 때 보는 이가 이해가 쉽게 되도록 하기 위해 작성해야 한다.

- line 7: 해당 메소드가 어떤 일을 하는지 명확하게 드러나도록 네이밍을 해야 한다.

- line 8: 계산 '결과'가 아닌 '어떤 데이터'가 담길지 드러나도록 네이밍을 해야 한다.

- line 9~20: n-depth의 조건문들로 눈을 혼란스럽게 하던 로직을 1-depth로 최적화한다. 로직을 정리한 후 코딩을 하면 더 깔끔한 모양새의 코드가 나온다.


논리를 잘 정리하지 않은 채로 코딩을 하거나, 리팩토링을 하지 않는다면 문제의 복잡도가 증가할수록 더욱 복잡하고 가독성이 결여된 코드가 작성될 수도 있다.

읽기 좋은 코드를 만들기 위해 신경 써야 할 것들은 무엇일까?





변수


변수명에는 담길 데이터를 나타낼 수 있도록 네이밍 한다.

변수명은 모호하지 않아야 한다.

예를 들어 서버에서 받은 이용자 정보 데이터를 `var data` 혹은 `var result`로 선언하고, 애매모호한 변수가 많다고 가정해보자. 선언부를 한참 지나 참조하는 부분에서 `getAddress(data[0])`를 보게 된다면 한눈에 어떤 데이터의 주소를 가져오는지, `data`가 어떤 데이터를 담고 있는지 단번에 알기 쉽지 않다. 

물론 선언부를 보면 어떤 데이터인지 알 수 있겠다만, 왔다 갔다 하는 것은 시선이 분산되고, 생각도 분산되고 번거로워진다. 위 코드를 아래와 같이 리팩토링을 하면 어떨까?

처음부터 명확하게 `var users`라고 선언해두면 참조부에서 매번 선언부를 다시 찾는 수고를 하지 않아도 된다.



너무 과한 함축 어를 사용하지 말자

짧은 코드가 항상 좋은 코드는 아니다. 아래의 예시를 보자.

`tt`가 무엇일까? 퀴즈 같다. `tt`에 값이 할당되는 시점을 알고, 어떤 데이터인지 기억해둬야 다음 로직이 이해가 될 것이다. 만약 이런 변수가 한 개가 아니라 여러 개라면 어떨까?

모든 함축적인 변수의 이름이 어떤 의미인지 기억하고 로직을 본다는 것은 한눈에 읽히지도 않을뿐더러 읽고 파악하는데 더 많은 시간을 들여야 한다. 

함축은 변수의 데이터와 의미를 해치지 않는 선이 적당하다.



비슷한 단어들로 혼돈을 주지 말라

사용자의 회원가입/로그인 시점을 가공하는 로직이 필요하다고 가정하자.

이 둘은 Date-type의 변수이고, 비슷한 의미라 date1, date2로 선언했다.

눈에 잘 들어오는가? 

역시나 모호하다. 변수명 뒤에 1, 2를 붙이거나 비슷한 단어를 사용하는 것은 가독성에 있어서 좋은 영향을 주지 않는다.

위와 `-at`을 접미사로 붙여서 선언하면, 자동으로 date-type의 변수임을 아는 것은 물론이고, 어떤 데이터를 담을 것인지도 명확하게 알 수 있다.


네이밍이 익숙하지 않은 상황에서 처음부터 변수에 신경을 쓰고 코드를 작성하려 하면 생각의 흐름이 끊겨 로직의 큰 그림을 그리는데 방해가 될 수도 있다. 처음에 연습하기 좋은 방법은 우선 본인 스타일대로 작성하되, 코드리뷰를 받기 전 리팩토링을 통해 코드를 점검하는 것이 좋다. 

그렇게 점점 발전해나가면 된다. :)





메소드


함수의 이름은 어떤 일을 하는지 명확하게 드러나도록 네이밍 한다.

무엇을 get 한다는 것일까? 위 단락에서 변수명을 명확하게 작성해야 한다는 의미와 상통한다. 하지만 변수명과 메소드의 네이밍 방법의 차이가 있다. 변수는 담길 데이터를 잘 나타낼 수 있도록 네이밍 하는 것이라면, 메소드는 어떤 일을 하는지 요약 및 기술할 수 있도록 네이밍 해야 한다.

아래의 예시를 보자.

읽기에 불편함이 없었는가? 

메소드는 어떤 ‘일'을 하는 로직의 한 묶음/덩어리므로 동사형으로 표현되어야 한다.

'세금을 계산하고, 결괏값을 `tax`에 assign 한다'라고 로직이 명확해졌다.

메소드의 이름은 두세 단어 정도로 이루어진 한 문장으로 요약할 수 있도록 네이밍 하는 것이 이상적이다.

이 말은 메소드가 책임지는 기능이 그 이상으로 복잡해지지 않아야 한다는 것을 의미하기도 한다.



한 메소드 내에서 한 가지의 기능만 명확하게 수행한다.

아래의 예시를 보고, 어떤 일을 하는 메소드인지 파악해보자.

일단 이름만 봐도 길다. 

스페이스 대신 camel-case가 익숙한 개발자라 해도 위의 메소드는 무슨 일을 하는 메소드인지 한눈에 파악하기 어렵다. 하물며, 내부 로직은 어떨까? 한 메소드 내에서 여러 가지 일을 처리하기 위해서는 코드의 길이가 길어질 것이고, 다양한 케이스에 대한 예외처리가 혼재되어 있어 복잡할 것이다. 메소드가 길고 복잡해지면 가독성을 해치게 된다.


추후에 tax를 계산해야 하는 로직이 다른 곳에서 필요하게 된다면 어떨까?

메소드를 확장해야 하는 경우라면 기능을 재활용하거나 분리하기는 더욱 어렵다. 물론 그때 가서 코드를 뜯어고쳐도 되겠지만, 복잡해져 버릴 데로 복잡해진 로직을 분리해내는 것에 많은 시간을 투자해야 할 수도 있다. 

가장 좋은 방법은 단일 메소드 단일 책임의 원칙을 지키는 것이다.

여러 개의 일을 하는 하나의 메소드 보다, 하나의 일을 제대로 하는 여러 개의 메소드가 재사용하기도 좋고, 부수효과에 대한 범위도 제어할 수 있다.



DRY 하게 작성하자. 

DRY(Do not Repeat Yourself)는 같은 일을 반복하지 말라는 의미이다.

(반의어로는 WET, Write Everything Twice, 혹은 We Enjoy Typing이 있다. 절묘하다.) 

무심결에 작성하다 보면, 같은 로직을 처리하는 코드가 여러 곳에 흩어져 있게 될 수 있다. 이럴 때에는 반드시 리팩토링의 과정을 거쳐 중복을 최소화해야 한다. 작성 시부터 중복을 고려해서 확장해 나가면 더욱 좋다. 중복을 최소화하지 않을 경우, 전체적인 코드의 길이도 늘어나고 로직에 오류가 있거나, 개선이 필요해질 때 모든 부분을 찾아다니며 고쳐야 한다. 중복을 제거하면 한 곳만 수정하면 될 일을 n번 하게 되는 것이다. n번 수정하다가 자칫 실수로 한 군데 빼먹으면 예상하지 못한 오류가 발생할 수 있다.


아래의 예제 코드를 리팩토링 해보자. 사용자들의 데이터를 관리하는 로직이며, 개인정보가 아직 입력되지 않은 준회원과 모든 개인정보가 담긴 정회원으로 분류가 되어있고, 준회원과 정회원을 사용자 정보를 각각 가져오는 로직이다. 

사용자가 준회원인지 정회원인지 판단하는 로직을 자세히 살펴보면 'true or false' 이분법으로 판단하고 있고, 판단하기 위한 근거가 2가지(주민등록 번호, 계좌번호)가 있다. 정회원 판단 기준이 늘어나게 된다면, 각각의 코드를 수정을 해야 할 것이다. 하지만, `type` parameter를 받아 아래와 같이 처리한다면, 중복되는 로직도 제거되고, 준회원/정회원을 판단하는 로직이 명확하게 한 곳에 드러나게 된다.

중복 로직 뿐만 아니라 유사한 로직도 공통부분을 추상화하여 리팩토링 할 수 있다.

중복된 부분을 하나로 합친다면 시스템 전체의 복잡도가 감소한다.

잘 정리된 코드는 변경사항이 미치는 파급범위를 예측할 수 있다. 파급범위를 예측할 수 있으면 예상하지 못한 오류가 나타날 가능성이 적어진다. 


초기 설계에서 중복을 제거하려는 노력은 중요하다.

초기에 잘 작성해뒀다 하더라도 유지보수 혹은 기능을 추가하다 중복 코드가 많이 생산되는 경우도 있으며, 복잡도가 증가하게 된다. 기능이 추가될 때마다 중복/유사 코드를 찾고 중복을 제거하려는 노력이 필요하다.






클린 코드에 관한 기법들은 책 한 권으로 나올 만큼 분량이 많다.

이 모든 것을 지키기는 현실적으로 어렵다고 생각한다. 위 글은 읽기 좋은 코드, 깨끗한 코드를 만드는 개발자가 되기 위해 중요하고 기본적인 방법들이 기술되었다. 읽기 좋은 코드, 깨끗한 코드를 만들기 위한 노력은 항상 해야 한다.


https://brunch.co.kr/@aria-grande/12


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari