Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 다익스트라
- 짝지어제거하기
- 딥러닝
- 클린코드
- deeplearning
- MachineLearning
- 1916
- GPT
- 백준
- 논문구현
- NLP
- 백준 1916 자바
- dijkstra
- 알고리즘
- 알렉스넷
- 디미터법칙
- 관심사분리
- Java
- 1107번
- 백준 1339 자바
- Alexnet
- 백준9095
- 자바
- 논문
- 논문리뷰
- 머신러닝
- cs231n
- 1261
- 3745
- 백준 1339
Archives
- Today
- Total
산 넘어 산 개발일지
Clean Code - 클래스 본문
객체지향 프로그래밍의 중심은 당연히 클래스이다.
클래스를 얼마나 체계적으로, 그리고 깔끔하게 설계하느냐에 따라서 코드의 품질을 따질 수 있다고 생각한다.
객체지향에서, 특히 자바에서 클래스 설계 방식은 굉장히 다양하다.
그 중 이번 챕터에서 나온 것들만 지키려고 노력해도 충분히 깔끔한 코드가 나올 것 같다.
클래스 구성
- 구성 순서
- static -> non-static
- public -> private
- 캡슐화
- 변수와 유틸리티 함수는 최대한 공개하지 않는 것이 좋다.
- 이 캡슐화를 깨는 것은 어디까지나 도저히 방법이 없을 때 최후의 수단으로 사용해야 한다.
- 다만 테스트 코드를 위해서는 protected 로 공개해줄 수도 있다.
책임
-
단일책임원칙 (SRP, Single Responsibility Principle)
- 클래스는 하나의 책임(기능)만을 가져야 하며, 클래스의 모든 요소들은 그 책임을 수행하는 것에만 집중해야 한다.
- 클래스나 모듈을 변경할 때는 변경하는 이유가 단 하나뿐이어야 한다.
- 클래스가 여러 책임을 가질 경우에는 클래스를 분할하여 클래스당 하나의 책임만 맡도록 해야 한다. 이 때 나누어지는 클래스들 간의 관계는 최대한 단순하도록 설계해야 하며, 만약 맡은 책임에 유사한 부분이 있다면 두 클래스 간의 부모클래스를 만들어 공유되는 부분을 부모클래스가 구현해주면 된다.
-
응집도
- 클래스의 메서드가 클래스 인스턴스 변수를 사용하는 정도
- 보통 메서드가 클래스 인스턴스 변수를 많이 사용할수록 응집도가 높다.
- 만약 응집도가 낮다면(ex 소수의 메서드만이 사용하는 인스턴스 변수가 있을 경우) 클래스를 쪼개야 하는 신호일 수 있다.
클래스 변경
-
변경 이유
- 요구사항의 변경이나 업데이트
- 낮은 응집도
- 클래스 일부에서만 사용되는 비공개 메서드
-
변경(구현)과의 격리
- 클라이언트 클래스(구현 담당)를 인터페이스와 추상 클래스로부터 분리
- 클라이언트 클래스는 대체로 수정될 가능성이 항상 높다. 그러나 이 변경 때문에 인터페이스와 추상 클래스까지 변경되면 안된다.
- 즉 클라이언트 클래스가 인터페이스와 추상 클래스에 의존적이여야 한다. 그 역이 성립하면 안된다.
- 클라이언트 클래스(구현 담당)를 인터페이스와 추상 클래스로부터 분리
-
결합도
- 클래스나 함수 같은 요소들이 다른 요소로부터 격리(변경에 대한 격리)되어 있으면 결합도가 낮다고 한다.
- 이는 주로 SOLID(SRP, OCP, LSP, ISP, DIP)를 지키면 따라오게 되어 있다.
-
의존관계 역전 원칙 (DIP, Dependency Inversion Principle)
- 하위 레벨 모듈의 변경으로부터 상위 레벨 모듈을 격리시키는 원칙
- 즉 상위 레벨에서 하위 레벨 모듈을 사용할 때, 바로 사용하지 않고 추상 레벨 하나를 만들어서 하위 레벨을 사용한다면 이를 지킬 수 있다.
- ex) Computer -> Keyboard => Computer -> AbstractKeyboard -> Keyboard
내 코드 돌아보기
class AuthSignupFragment : Fragment() {
lateinit private var authActivity: AuthActivity
private var inputs : ArrayList<EditText> = arrayListOf()
private var messages : ArrayList<TextView> = arrayListOf()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {}
fun initialSetUp(view : View) { }
private fun setFocusChangeListener(view: View) { }
private fun checkValidation(input : EditText, text : String) : Boolean{}
private fun checkEmail(input : EditText, message : TextView, email : String) : Boolean{}
private fun checkPassword(input : EditText, message : TextView, password : String) : Boolean{}
private fun checkPasswordCheck(input : EditText, message : TextView, password : String, pw_check : String) : Boolean {}
private inner class SignupClickListener(val view : View) : View.OnClickListener {}
override fun onActivityCreated(savedInstanceState: Bundle?) {}
}
(간략하게 하기 위해 함수 내용은 생략했다)
1. SRP 원칙
- AuthSignupFragment는 Signup에 대한 페이지를 보여주는 뷰이다.
- 그러나 checkValidation(), checkEmail(), checkPassword(), checkPasswordCheck() 같은 함수들은 AuthSingupFragment의 책임과 직접적인 관련이 없다. 좀 더 자세히 말하면, AuthSignupFragment에서 다루기에는 추상 레벨이 너무 낮다.
- 따라서 이들을 따로 클래스로 쪼개주면 더 좋았을 것 같다.
2. 응집도
- SRP 원칙을 위반하는 4 가지 함수들을 제외하면 initialSetUp(), setFocusChangeListener() 메서드와 SignupClickListener 클래스가 남는다. (onActivityCreated()는 Fragment의 생명주기와 관련된 메서드이므로 제외했다.)
- 이 3가지 메서드의 매개변수는 모두 페이지의 root view가 할당되는 view 변수이다.
- 따라서 이 view를 인스턴스 변수로 빼준다면 매개변수도 적어지고 이 클래스의 결합도도 올라갈 것이다.
수정된 코드
class AuthSignupFragment : Fragment() {
lateinit private var authActivity: AuthActivity
lateinit private var rootView : View
private var inputs : ArrayList<EditText> = arrayListOf()
private var messages : ArrayList<TextView> = arrayListOf()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {}
fun initialSetUp() {}
private fun setFocusChangeListener() {}
private inner class SignupClickListener() : View.OnClickListener {}
override fun onActivityCreated(savedInstanceState: Bundle?) {}
}
1. SRP 원칙
- checkValidation(), checkEmail(), checkPassword(), checkPasswordCheck() 함수들을 AuthValidator 클래스의 메서드로 포함시켜줬다.
- 이로써 AuthValidator는 Validation이라는 책임에 더 집중하게 되었고, AuthSignupFragment에서는 Validation이라는 책임을 다른 클래스에 위임할 수 있었다.
2. 응집도
- 기존의 view : View를 매개변수로 받던 함수들에서 이 매개변수를 지우고 클래스 인스턴스 변수에 rootView를 추가해주었다.
- 재밌게도, setFocusChangeListener()에서는 정작 rootView가 필요하지도 않았다.
- 이로써, 각 함수들이 rootView, inputs 등의 인스턴스 변수들을 사용하는 빈도수가 높아져 응집도를 높였다.
포인트
1. 클래스당 하나의 책임!
2. SOLID 원칙
3. 응집도
4. 결합도
느낀점
직접 코드를 수정해보니 확실히 더 기억에 잘 남을 것 같다.
당장에 이 모든 것들을 지키면서 코딩을 할 수 있다고는 생각하지 않는다.
하지만 적어도 SOLID, 응집도, 결합도는 생각하면서 코딩을 하려고 노력한다면 지금보다 훨씬 더 체계적으로 클래스들을 구성할 수 있을 것 같다.
그리고 SOLID에 대한 것도 공부해서 한번 정리해보면 좋을 것 같다.
아 GOF도 정리해야 한다.
뭔가 모르는 용어를 이렇게 하나씩 알아가는 것도 재미가 생기고 있다.
다만 시간이 오래 걸리는데... 조금 더 내게 시간이 많았으면 좋겠다 ㅠㅠ
'Study > CleanCode' 카테고리의 다른 글
Clean Code - 창발성 (0) | 2021.03.18 |
---|---|
Clean Code - 시스템 (0) | 2021.03.10 |
Clean Code - 단위 테스트 (0) | 2021.03.03 |
Clean Code - 경계 (0) | 2021.03.01 |
Clean Code - 객체와 자료구조 (0) | 2021.02.21 |
Comments