feat. SRP
우리는 테스트 코드를 작성하기 힘든 코드를 많이 보았고 많이 만들어 냈을것이다. 이번 글은 어떻게하면 테스트하기 좋은 코드를 만들어 갈것인지에 대한 이야기이다(원문).
클래스에는 너무 많은 책임이 있으면 안된다. 클래스는 SRP(Single Responsibility Principle) 단일 책임의 원칙을 지켜야한다. SRP을 지킴으로써 low coupled design을 얻을 수 있고 특정 기능을 수정할 때 여러 클래스를 수정해야 하는 경우가 생기는것을 방지하며 이는 유지보수성이 증가됨을 뜻한다.
- 클래스가 무엇을 하는지를 종합해서 표현할 때 'and'가 포함되는 경우
- 새로운 멤버가 클래스를 읽고, 이해하는 것이 도전적이고 어려운 일인 경우
- 클래스가 몇몇 메소드에 의해서만 사용되는 필드를 갖는 경우
- 클래스에 파라미터만 사용하는 static method가 있는경우
- 디버그하기 어려움
- 테스트하기 어려움
- 확장 불가능한 시스템이 될 가능성이 높아짐
- 클래스 네이밍이 어려움. 네이밍이 어렵거나 모호하다는것은 코드의 책임이 모호함을 드러낸다. 명확한 이름이 있다면 초점을 유지할 수 있고 불필요한 책임을 걷어낼 수 있다.
SRP를 지키지 않은 클래스를 일컫는 말
- Kitchen Sink
- Dumping Ground
- Class who's Behavior has too many "AND's"
- First thing's KILL ALL The Managers (*See Shakespeare)
- God Class
- You can look at anything except for this one class
- 클래스가 하는일을 한 문장으로 말해보자. 만약 이 문장에 AND가 포함되어 있으면 너무 많은 일 을 하고 있는것이다.
- 클래스의 이름은 그것이 하는 일을 표현해야 한다.
- 이름은 역할을 명확하게 전달해야한다.
- 클래스에 manager, Utility 또는 Context와 같은 광범위한 이름이 필요한 경우 너무 많은 작업을 수행하고 있다는것을 뜻한다.
- 정확하고 완전하게 이름을 지정할 수 있으면 책임을 더 잘 분리하게 되고 응집력 높은 클래스를 디자인 할 수 있다. 만약 이름을 지정할 수 없다면 더 여러개의 클래스로 나누어야 할 수 있다.
- 과도한 스크롤
- 많은 Fields
- 많은 methods
- 관련이 없는 / 관련된 메서드의 덩어리
- 많은 collaborators(passed in, constructed inside the class, accessed through statics, or yanked out of other collaborators)
- 클래스가 딱 봐도 '너무 크다!'고 느껴짐. 이것은 클래스의 행동을 이해하기 어렵다.
- 클래스가 아무런 행동도 하지않는 dumb collaborators와 협력 중이다.
- public 메서드 뒤에 숨겨진 interactions
클래스가 아무런 행동도 하지않는 dumb collaborators와 협력 중인경우 클래스가 collaborators를 대신하여 모든 작업을 수행하고 있을것이다. 이 책임은 collaborators에게 있음에도 말이다. 이런 클래스를 God Class라고 부를수 있다. 이것은 종종 Primitive Obsession에 의해 발생한다. 디자인 관점에서 이런 구성은 유연성의 포인트를 숨기며, 테스트 관점에서 개별적으로 테스트하려는 이음새를 얻을수 없게 한다. 단일 책임에 의한 캡슐화하는것은 좋은 정보 은닉이나 책임 간의 상호 작용을 캡슐화하는 것은(많은 책임을 가진 하나의 클래스를 가짐으로써) 협력을 취약하게 만들기 때문에 일반적으로 좋지 않다.
- 하나의 책임을 구분하자
- 네이밍을 잘 하자
- 각 책임에 대해 별도의 클래스로 기능을 추출하자
- 한 클래스는 다른 클래스 사이를 중재하는 숨겨진 책임을 수행할 수 있다.
- 이렇게 하면 더 쉽게 개별적으로 테스트가 가능해진다
- 새로운 기능에 대해서는 SRP를 지키도록 클래스를 만들자
- 기존 클래스에 대해 작업할 때(즉, 다른 조건 추가 등) 해당 책임을 수행하는 클래스를 추출하자. 이것은 레거시 클래스에서 청크를 가져오기 시작하고 각 청크를 독립적으로 테스트 할 수 있다(의존성 주입 사용).
- 위의 구성이 어색하거나 부자연스러울 수 있다. 이 어색함에도 불구하고 오늘 God Class가 성장하는것을 막기위한 조치를 취해야만 한다. 그렇지 않으면 전지전능한 Class가 되어 점점 걷잡을수 없을 것이다.
- 같은 책임을 갖는 기능. 즉 같은 부류인지 판단해보자. 해당 메서드 들을 누가 사용하는지 메서드들의 Client에 의해 부류는 결정이된다. '누가 해당 메서드의 변경을 유발하는 사용자인가?!'. 예를들어 save()라는 메소드의 로직의 변경이 필요한데 그럼 누구때문에 save가 변경되는가? 이런걸로 메소드들을 grouping 할 수 있다.
- SRP는 책임은 사용자에 관한것이다.
- 책임은 SW의 변경을 요청하는 특정 사용자들에 대해 클래스/함수가 갖는것.
- 해당 클래스를 사용하는 Client들이 있을텐데 그 Client들에 의해서 해당 클래스가 바뀌는것이므로 어떤 Client들에의해서 사용되는지를 잘 grouping해야한다
- 결국 책임이란 ! -> 변경의 근원(원인) 으로 볼 수있고, 변경의 원인이 같은 것은 같은 책임으로 볼 수 있다.
- 클래스의 변경을 요구하는 사용자(Client)들은 Actor(서로 다른 Needs, Expectation을 가짐)이다. 즉, Client들이 수행하는 Role에 따라 나눠야한다. 특정 Role을 수행할 때 Actor가 되는것이다.
- 책임은 단순한 Client로 나뉘는것이아니라 특정 Role의 분류로 나뉜 Actor에 따라 나뉘는것이다.
- Actor의 요구사항이 변경되면 Actor가 사용하는 method들의 집합이 변경된다.
- 책임이란것은 특정 Actor의 요구사항을 만족시키기 위한 일련의 함수의 집합
- Actor의 요구사항 변경이 일련의 함수들의 변경의 근원이 된다.
Primary and Secondary Values
Secondary value of SW is it's behavior: 현재의 SW가 현재 사용자의 현재 요구 사항을 만족하는가?
Primary Value of SW is it's Soft: 지속적으로 변화하는 요구사항을 수용(tolerate, facilitate)하는것.
대부분의 SW는 현재의 요구사항을 잘 만족하지만 변경하긴 어렵다. 두 가지 모두 중요하지만 요구사항은 자주바뀌고 SW가 이 요구사항을 잘 수용할 수 있어야 한다. SW의 Primary Value는 앞으로의 요구사항을 수용할 수 있는가이다. Agile 에서 동작하지만 고칠 수 없는 SW와 고칠 수 있지만 동작하지 않는 SW가 있을때 고칠수 있는 코드가 더 중요하다고 한다. 지금까지의 글은 테스트 가능한 코드를 작성법이였지만 글을 보면 테스트 가능한 코드는 결국 Clean한 코드로 가기 마련이다. 우리는 SW의 가치를 지키기위해 테스트 작성이 용이하고 Clean한 코드를 작성해 가야한다. 언제까지 '옛날엔 잘 돌았는데..'라고 말할수는 없지 않은가?
지금까지 총 4편의 Testable Code에 대해 알아 보았다.
The only way to go fast, is to go well - Robert C. Martin