완성한 프로그램이라 할지라도 사용자의 편의를 위해 끊임없이 발전하고 변경된다.
그렇기 때문에 '읽기 쉬운', '이해하기 쉬운' 코드는 새로운 기능을 추가하거나 수정을 할 때 언제나 강력한 장점이 있다.
프로그램 구축 시 좋은 성능, 니즈에 맞는 기능, 최적화 만큼이나 중요한 것은 유지보수하기 쉬우며 수정하기 좋은 코드를 짜는 것이라고 생각한다. 최근 아주 다양한 IT플랫폼, 서비스들이 많이 생겨나면서 유저들의 눈높이도 높아지고 있다.
유입이 늘어나고 트래픽이 증가하면서 프로그램 장애가 발생할 수도 있고, 불편한 UX나 기능을 개선해야 될 때도 있으며, 유저의 니즈에 맞춰 새로운 기능을 추가해야 될 때도 있다.
보통의 경우 각자가 개발한 코드를 언젠가 누군가는 본다. 이 때 코드를 이해하거나 수정하기 어려우면 유저들의 요구에 빠르게 대응하기 어렵다. 이 때 유용한 개념이 리팩토링이며 다양한 서적에서 그 방법을 설명하고 있다.
최근 고민하고 있는 부분은 어떻게 하면 유지보수하기 쉬운 코드를 짤 것인가,
유저들의 요구에 빠르게 대응하기 위해 필요한 역량은 무엇인가
사용자는 어떤 것에 불편을 느끼고 있으며 무엇을 진정으로 필요로 하는가이다.
이 포스트는 그 중 리팩토링에 초점을 맞추어 작성하였다.
정말 좋아하는 책 중 하나인 마틴 파울러의 리팩토링 책에서의 기법을 실무에서도 종종 활용하곤 하는데, 최근 개발 프로젝트에서 유용하게, 혹은 자주 사용해서 좋았던 기법들을 정리해 두고자 한다.
- 함수 추출하기
프로그램을 짜다 보면 한 개의 메서드에서 굉장히 다양한 일을 하도록 코드를 짜기 십상인데 이럴 경우 나중에 수정할 일이 있을 때 직관적이지 않아서 이해하는 데 시간이 걸리게 된다. 그렇다고 너무 지엽적인 기능만을 위해 따로 함수씩이나 만드는 것도 좋은 방법은 아니지만 함수 하나가 북도 치고 장구도 치고 꾕가리도 치게 되면 재사용성이 낮은 코드가 탄생하게 된다. 하지만 이 때 함수의 적절한 추출을 통해 각각의 코드다발을 모듈화해서 관리하게 되면 해당 함수가 어떤 일들을 하고 있는지 보다 쉽게, 그리고 한 눈에 파악이 가능하다. 게다가 함수 추출을 통해 모듈화된 코드는 함수 B, 함수 C에서도 잠재적으로 재사용될 이점을 제공한다.
개인적으로는 코멘트를 달아야 이해가 될 정도의 코드 다발은 별도의 메서드로 빼는 편이다.
(P.S.)
앞서 언급했듯이 함수추출이 리팩토링에 있어 중요하긴 하지만
이런 간접 호출이 유용하기만 하진 않다. 쓸데 없이 과하게 쓰이면 오히려 거슬릴 뿐 가독성을 해친다.
때문에 파울러 또한 책에서 인라인 하기 기법을 소개하고 있다.
예시를 한 번 만들어 봤다.
A라는 음식을 만드는 함수가 있다고 하자.
이 함수 안에는 A의 비법소스 만드는 코드, A의 양조절을 하는 코드, A의 가격에 대한 코드와 A의 특별 데코레이션에 대한 A만을 위한 코드가 가득하다.
게다가 이 각각의 코드는 한 줄 정도의 짧은 코드이다. 이를 굳이 함수로 추출해야 될까.
이런 딜레마는 개발하면서도 여러 번 겪었는데 결론은 "충분히 명확하게 이해가 가능한 짧은 코드이며 재사용성이 전무"한 경우 굳이 함수로 빼지 않는다이다.
개발자라면 디버깅은 필수인데, 개인적으로 에러를 찾을 때 한 줄 한 줄 씩 신중하게 볼 때가 많다.
그런데 이런 함수 추출로 인한 간접 호출이 과하게 많으면 여기 저기 왔다갔다 하면서 디버깅할 때 오히려 정신이 산만해진다. 그러므로 함수 추출하기 기법은 만능이 아니다.
리팩토링 한다는 게 더 프로그램을 복잡하게 만들 수 있는 만큼 리팩토링할 대상을 '선별적'으로 잘 골라내야 한다. 이는 단번에 된다기 보다는 경험이 쌓일 수록 점점 최적의 방법을 찾아가는 것 같다.
- 죽은 코드 제거
프로그램 동작에 필요 없는 코드들은 개발을 하다보면 굉장히 많이 생긴다. 코드를 읽을 떄 이런 불필요한 코드들은 가독성만 해치므로 그때그때 지워주는 것이 가독성 향상에 많은 도움이 됐다. 죽은 코드 제거는 마치 집안 일과 같다.
나중에 모아서 하면 되겠지 생각하다가 산더미 처럼 쌓이면 어떤 거 부터 해야될지 갈피를 못 잡을 우려가 있다.
그때그때 불필요한 코드는 제거해 두는 게 훗날의 내게 짐을 덜어주는 길이다.
- 보호구문
비즈니스에는 아주 다양한 케이스가 있으며 예외사항이 있다.
이때 분기가 되어도 하나로 합쳐지는 로직이 있는가 하면 분기가 된 채로 로직이 갈라져 다시 하나로 귀결되지 않는 경우도 있다.
이런 경우 대부분 return문을 적절히 활용하면 훨씬 더 깔끔한 코드를 짤 수 있었다. 보호구문은 알고리즘 문제에서도 자주 쓰이는 걸 볼 수가 있는데 목적을 달성하면 빠져나오는 유용한 기법으로 시간 복잡도가 좋아진다.
특히 모듈 프로그램의 전체 수행 시간을 많이 단축시켜 줄 수 있으므로 이 보호구문을 적절히 사용하여 프로그램이 빠르게 동작하게 만들면 칭찬을 받을 것이다(?)
추가로, 보호구문을 잘 활용하면 if ~ else if ~ else if~ else if ~ (이제 그만 나오겠지 할 떄 또) else if~ else if~ else
같은 코드를 피할 수 있다. 이런 구조는 코드 가독성이 대체로 좋지 않은 편이다.
▶
어떤가. 코드 짜기에 정답은 없다. 그리고 switch 문으로 정리 할 수도 있다. 코드 취향은 음악 취향 만큼이나 사람마다 다양하다.
기호에 맞게 정리해보자.
- 객체지향
모듈화와 비슷한 맥락인데, 객체로 만들어 두면 같은 코드를 두 번 재차 작성할 필요없이, 만들어둔 객체를 '재사용'🔄이 가능해 지기 때문에 코드도 더 깔끔하게 변신한다.
- 로깅
프로그램의 로직 흐름 중간중간 성실히 로깅을 해둔다면 나중에 버그가 나타났을 때 어디 부분에서 문제가 됐는지 빠르게 찾을 수 있다. 좋은 습관인 것 같다.
- 조건 합치기
조건A,B의 동작의 결과가 같다면 굳이 이 코드를 따로따로 작성할 필요가 없다. 프로그램을 짤 때 항상 염두하고 있는 부분이다. 중복되는 코드가 여기저기 있다면, 만약 수정해야 될 일이 생길 때 그 것들을 '모두' 수정해야 한다. 굉장히 불필요하고 시간도 많이 든다. 되도록 중복되는 코드는 '일원화'하여 관리하는 편이 유지보수하기 좋다.
가령 이런 경우이다.
if( 조건A ) return (thisIsJustRandomVariable * thisIsJustRandomVariableNumber2) / 300
if( 조건B ) return (thisIsJustRandomVariable * thisIsJustRandomVariableNumber2) / 300
if( 조건C ) return (thisIsJustRandomVariable * thisIsJustRandomVariableNumber2) / 300
▼
if( 조건A || 조건B || 조건C ) return (thisIsJustRandomVariable * thisIsJustRandomVariableNumber2) / 300
전자의 경우 변수이름이 바뀌거나 수식이 변경되었거나 하면 수정을 3번 해야한다.
후자의 경우 1번만 수정하면 된다. "얼마나..보기에도 좋은가. !" 중복을 없앴을 뿐인데 내가 할 일은 3분의 1로 줄었다. 내가 할 일만 줄었을까, 이 코드를 미래에 맡을 사람들의 수고까지 덜었다. 그렇다. 모듈화라든지, 객체지향이라는 것은 그런 것이다. 당신의 수고를 줄여주는 것.
애초에 개발이라는 것이 그런 것 아니겠는가.
- 매개변수그룹을 객체화 해서 넘기기
앞에서 함수 추출 기법을 통해 함수로 코드를 쪼갠다 하더라도 함수가 아래와 같이 많은 매개변수를 받는 다면 읽기 어렵다.
이를 클래스화 해서 넘겨주면 가독성이 대폭 늘어나고, 이 클래스로 바꾼 매개변수 그룹은 다른 함수에서도 재사용될 수 있다.
void Func(string A, string B, string C, int A, int B, int C)
▼
void Func(Param p)
- 의미가 명확히 드러나는 이름 설정의 중요성. 🔤
클린코드에서는 이를 아래와 같이 설명하고 있다.
"좋은 이름을 짓는 것이 시간 비용이 들지라도 결국 그 시간 보다 더 많은 시간을 단축시켜 줄 것이다."
프로그램을 짜면서 다양한 엑셀 테이블 포맷에 맞춰 마케팅 데이터를 뿌려줬었는데 그 각 테이블의 고유한 디자인 포맷을 DB에서는 넘버링으로 유형을 나누었다. 그 와중 프로그램에서 각각의 테이블에 따라 다른 로직을 적용시켜주어야 되는 일이 있었는데 넘버링으로 되어있다보니 코드 가독성이 떨어졌으며 어떤 테이블을 의미하는 건지도 불분명하여 오히려 파악하는 데 시간이 걸렸다. 테이블의 유형을 넘버링으로 한 것은 프로그램에서는 좋은 이름은 아니었던 것이다.
To Be Updated..
참고책 : 클린코드, 리팩토링2판, 내경험