코드 품질이 소프트웨어 품질에 어떤 영향을 미칠까?
고품질 코드 | 저품질 코드 | |
최초 요구 사항 | 완전하게 충족 | 경계 조건을 처리하지 못하기 때문에 완전하게 충족하지 못함 |
요구 사항 변화 | 사소한 추가 작업 필요 | 대규모의 코드 변경 및 리팩터링 필요 |
오류 발생 시 | 시스템이 복구되거나 부분적으로 작동 | 시스템은 미리 정의되지 않은 상태에 놓이고 데이터는 손상될 가능성이 있음 |
처음 접하는 상황 | 명백히 예상되지 않은 상황도 처리 | 시스템은 미리 정의되지 않은 상태에 놓이고 데이터는 손상될 가능성이 있음 |
시스템 공격 | 시스템은 완전한 상태이고 손상되지 않음 | 시스템은 미리 정의되지 않은 상태에 놓이고 손상될 가능성이 있음 |
좋은 코드가 좋은 소프트웨어를 만들기 위한 유일한 조건은 아니지만, 중요한 조건 가운데 하나다.
고품질의 코드는 신뢰할 수 있고, 유지보수가 쉬우며, 요구 사항을 충족하는 소프트웨어악 될 가능성을 최대한으로 높여준다. 저품질의 코드는 그 반대다.
코드의 품질을 판단하는 것은 굉장히 주관적이다.
그래서 책의 저자는 고품질의 코드를 작성하는 네 가지 기준을 마련했다.
- 작동해야 한다.
- 작동이 멈춰서는 안 된다.
- 변화하는 요구 사항에 적응해야한다.
- 이미 존재하는 기능을 또 다시 구현해서는 안 된다.
1. 코드는 작동해야 한다.
사실 너무 당연한 말이다. 코드가 작동하지 않으면 그건 확실히 문제가 있다.
책의 저자가 의도한 바를 좀 더 살펴보자.
코드의 첫 번재 목표는 그것이 애초 작성된 목적대로 동작해야 한다는 것이다.
그러니까 단순히 동작하는 것이 아니라, 코드를 작성하려 했던 목적에 부합하게 작동해야 한다는 것이다.
2. 코드는 작동이 멈추면 안 된다.
처음 이 문장을 읽었을 땐, "이게 뭐야 당연한거 아니야? 작동이 멈추면 어떡해" 라고 생각했다.
하지만 내가 생각한 것과는 조금 다른 의미였다.
코드 작동은 매우 일시적일 수 있다. 오늘은 잘 돌아가지만 어떻게 해야 내일 혹은 1년 후에도 여전히 작동하게 할 수 있을까?
코드는 고립된 환경에서 홀로 실행되는 것이 아니기에 주변 상황이 바뀌면서 동작이 멈출 수 있다.
- 코드는 다른 코드에 의존할 수 있는데, 그 코드가 수정되고 변경될 수 있다.
- 새로운 기능이 필요할 때 코드를 수정해야 할 수도 있다.
- 우리가 해결하려고 하는 문제는 시간이 지남에 따라 변경된다. 소비자 선호, 비즈니스 요구, 고려해야할 기술 등이 바뀔 수 있다.
현재는 잘 돌아가지만 미래에 이런 것 중 하나가 변경된 경우, 제대로 동작하지 않는다면 그 코드는 별로 유용한 코드가 아니다.
3. 코드는 변경된 요구 사항에 적응할 수 있어야 한다.
한 번 작성하고 다시는 수정되지 않는 코드는 거의 없다. 아래와 같은 상황들로 코드는 수정 될 수 있다.
- 비즈니스 환경이 변한다.
- 사용자 선호가 변한다.
- 가정이 더 이상 유효하지 않다.
- 새로운 기능이 게속 추가된다.
책에선 변화를 예측하는 양 극단의 예시를 보여준다.
시나리오 A
앞으로 요구 사항이 어떻게 변할지 정확히 예측하고 모두 대응하려고 노력한다. 변화할 수 있는 모든 방면을 고려하다가 많은 시간을 사용한다. 그러다 3개월이면 끝날 일을 1년간 붙들고 있는다. 결국엔 경쟁자가 먼저 시장에 나오고 결국 시간 낭비가 되어버렸다.
시나리오 B
요구 사항이 변할 수 있다는 사실을 배제한다. 오로지 현재를 충족시키기 위한 코드를 작성한다. 3개월 이내에 첫 번째 버전을 출시하지만 초기 사용자의 피드백을 반영하기 위해 모든 것을 버리고 처음부터 다시 짠다. 매번 이를 반복한다. 결국 변화에 대응이 늦어 경쟁에서 밀리고 만다.
두 시나리오는 요구 사항 변화를 바라보는 양 극단의 경우이다. 우리는 A와 B 사이 어딘가에서 최적의 자리를 찾아야한다.
요구 사항의 변화를 완전히 예측할 순 없다. 하지만 이 책에서 적응성 높은 코드를 작성하기 위한 몇 가지 기술들을 알려줄 것이라고 한다. (굉장히 기대중... 근데 일단 1장은 아님)
4. 코드는 이미 존재하는 기능을 중복 구현해서는 안 된다.
한 줄로 요약하면, 프레임 워크나 언어에서 이미 잘 구현해 놓은 것을 굳이 다시 구현하지 말라는 말이다.
언어나 프레임워크에서 지원하는 기능들은 이미 수많은 사람들이 사용하고 그만큼 검증된 로직이다. 근데 그걸 굳이 시간 들여서 다시? 과연 더 잘 만들 수 있을까?
- 시간과 노력을 절약한다. - 이상한데 시간쓰지 말고 차라리 기능 하나를 더 개발해라
- 버그 가능성을 줄여준다. - 이미 철저히 테스트 된 코드다. 적어도 너가 만든 것보단 안전할지도?
- 기존 전문지식을 활용한다. - 예를 들어 바이트 코드를 이미지로 생성하는 코드를 관리하는 팀은 인코딩에 대해 전문가일 것이다. 나중에 추가적인 기능이 생길 수도 있고 우린 그때 전문가들의 업데이트를 받을 수 있다.
- 코드가 이해하기 쉽다. - 자신만의 코드가 아닌 표준화된 방법으로 코드를 작성해야 다른 개발자들이 파악하기 쉽다.
방금까지 고품질의 코드를 달성하기 위한 목표를 살펴봤다면, 이제 좀 더 구체적으로 고품질의 코드가 되기 위한 핵심 요소를 살펴볼 것이다.
- 코드는 읽기 쉬워야 한다.
- 코드는 예측 가능해야 한다.
- 코드를 오용하기 어렵게 만들어라.
- 코드를 모듈화하라.
- 코드를 재사용 가능하고 일반화할 수 있게 작성하라.
- 테스트가 용이한 코드를 작성하고, 제대로 테스트하라.
1. 코드는 읽기 쉬워야 한다.
코드 리뷰를 하거나 누군가 내 코드를 사용할 때, 사람들은 내 코드를 읽어야한다.
이때 코드 가독성이 좋지 않다면 다른 개발자들은 내 코드를 읽는데 많은 시간을 들여야 한다.
또한 잘못 이해하거나 중요한 것을 놓칠수도 있다.
코드도 레시피랑 똑같다. 읽기 쉽게 만들자.
2. 코드는 예측 가능해아 한다.
아무리 도움 되고 멋진 코드를 작성한다 해도 예상을 벗어난 동작은 위험할 수 있다.
책에서 좋은 예시를 들어주었지만 내용이 너무 길기 때문에 생략하도록 하겠다. (15페이지를 보면 알 수 있다)
여튼 아무리 멋진 기능이어도 다른 사람들이 예측하기 어렵다면, 비정상적으로 작동되거나 오류를 발생시킬 수 있다.
3. 코드를 오용하기 어렵게 만들어라.
책에선 소켓으로 예시를 든다. TV 뒷면에 전원 소켓과 HDMI 소켓이 있다.
일반적으로 전원 소켓과 HDMI 소켓은 형태가 달라 서로 바꿔 꽂을 수 없다.
하지만 만약 둘의 형태가 똑같이 생겼다고 가정해보자. 그럼 사용자는 종종 실수로 바꿔 꽂는 일이 발생할 것이다.
HDMI 케이블을 전원 소켓에 꽂는다면 아마 TV는 동작하지 않을 것이다. 그러나 전원 케이블을 HDMI 소켓에 꽂는다면 그대로 TV가 폭발할 수도 있다.
우리가 작성한 코드는 종종 다른 코드에 의해 호출될 것이다.
우리 코드도 케이블이나 소켓처럼 어딘가에 꽂히거나 무언가가 꽂힐 수 있다. 이때 같은 모양의 소켓처럼 문제를 일으키지 않도록 만들자.
4. 코드를 모듈화하라
모듈화가 잘 되어있다면 어딘가 고장났을 때, 혹은 변경 사항이 발생했을 때 해당 부분만 갈아끼우면 된다.
모듈화가 되어있지 않다면 작은 부분 하나 때문에 전체를 바꿔야할 수도 있다.
좀 더 자세히 말하자면
모듈화된 시스템의 주요 특징 중 하나는 인터페이스가 잘 정의되어 서로 다른 구성 요소 간 상호작용하는 지점이 최소화된다는 점이다.
5. 코드를 재사용 가능하고 일반화할 수 있게 작성하라
재사용성(reusability)은 어떤 문제를 해결하기 위한 무언가가 여러 가지 다른 상황에서도 사용될 수 있음을 의미한다. 핸드 드릴은 벽, 바닥 판 및 천장에 구멍을 뚫는 데 사용할 수 있기 때문에 재사용 가능한 도구다. 문제는 동일하지만(드릴로 구멍을 뚫어야 한다) 상황은 다르다(벽을 뚫는 것과 바닥을 뚫는 것과 천장을 뚫는 것)
일반화성(generalizability)은 개념적으로는 유사하지만 서로 미묘하게 다른 문제들을 해결할 수 있음을 의미한다. 핸드 드릴은 구멍을 뚫는 데 사용될 뿐만 아니라 나사를 박을 때도 사용될 수 있어서 일반화성을 갖는다. 드릴 제조사는 무언가를 돌린다는 것은 드릴로 구멍을 뚫는 일과 나사를 돌려 넣는 일에 둘 다 적용할 수 있는 일반적인 문제라는 것을 알고 두 가지 문제를 다 해결할 수 있는 일반화된 도구를 만들었다.
코드도 재사용할 수 있고 일반화되어 있으면 우리는 그 코드를 코드베이스의 여러 부분에서, 그리고 하나 이상의 상황에서 상요할 수 있고, 여러 가지 문제를 해결할 수 있다. 이는 시간과 노력을 절약해준다.
6. 테스트가 용이한 코드를 작성하고 제대로 테스트하라.
테스트는 프로세스에서 중요한 역할을 한다.
- 버그나 제대로 동작하지 않는 기능을 갖는 코드가 코드베이스에 병합되지 않도록 방지
- 버그나 제대로 동작하지 않는 기능을 갖는 코드가 배포되지 않도록 막고 서비스 환경에서 실행되지 않도록 보장
테스트가 중요하다는 말은 많이 들어봤을 것이다. 하지만 한 번 더 강조하자면 아래 같은 이유에서이다.
- 소프트웨어 시스템과 코드베이스는 너무 크고 복잡해 한 사람이 모든 세부 사항을 알 수 없다.
- (똑똑한 개발자라 해도) 사람은 실수를 하는 존재이다.
테스트가 용이하다는 우리가 테스트할 실제 코드가 테스트하기 적합하다는 것을 의미한다.
테스트 용이성은 모듈화와도 깊은 연관이 있다. (잘 분리되어 있어야 테스트하기 편하지 않겠는가)
책에서 말하는 가장 흔히 볼 수 있는 테스트는 다음 세 가지가 있다.
- 단위 테스트(unit test) - 개별 함수나 클래스와 같은 작은 단위의 코드를 테스트한다.
- 통합 테스트(integration test) - 여러 구성 요소들과 하위 시스템이 잘 연결되어 제대로 작동하는지 테스트한다.
- 종단간 테스트(end-to-end test) - 처음부터 끝까지 전체 소프트웨어 시스템에서 작동의 흐름을 테스트한다.
고품질 코드 작성은 일정을 지연시키는가?
결과적으로 말하면 처음에는 더 오래걸릴지 몰라도 향후엔 훨씬 더 이득을 볼 수 있을 것이다.
책에서 선반을 설치하는 예시를 들어 설명하지만 길어서 생략하도록 하겠다.
사실 이 부분은 코드를 치며 직접 경험해보면 더욱 확실하게 느껴질 것이다.
진짜 아무생각 없이 때려박은 코드는... 나중에 보기도 수정하기도 굉장히 힘들다.
사이드 프로젝트에서 한 액티비티에 모든 코드를 때려박아서 3~400 줄이었는데..
나중에 그 코드를 다시 볼려고 하니 머리가 지끈거려서 노트북을 덮고 싶었다.
우리 모두 고품질 코드를 작성하려고 노력해보자!
'독서 > 좋은 코드, 나쁜 코드' 카테고리의 다른 글
2장 추상화 계층 (0) | 2023.08.08 |
---|