산 넘어 산 개발일지

Clean Code - 클래스 본문

Study/CleanCode

Clean Code - 클래스

Mountain96 2021. 3. 4. 15:56

객체지향 프로그래밍의 중심은 당연히 클래스이다.

클래스를 얼마나 체계적으로, 그리고 깔끔하게 설계하느냐에 따라서 코드의 품질을 따질 수 있다고 생각한다.

객체지향에서, 특히 자바에서 클래스 설계 방식은 굉장히 다양하다.

그 중 이번 챕터에서 나온 것들만 지키려고 노력해도 충분히 깔끔한 코드가 나올 것 같다.

 


클래스 구성

  • 구성 순서
    • 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