다시 상쾌한 월요일의 시작이다. 그래도 서서히 바쁜 일들이 지나가는 느낌이다. 그것은 좋은점. 안좋은점은 진행하고 있던 프로젝트의 NextJS 파트에서 정말......미묘한 현상을 발견했다. 이것도 정리해서 글로 쓰고 해야겠는데, 원인을 전혀 모르겠다는...성능을 위해 많은 기능을 제공하는 것이 오히려 간단한 상황에 대한 해결을 복잡하게 하는 느낌이 없지 않은 느낌을 받는다. 예를들면 캐싱. 기본적으로 캐싱을 어플리케이션의 곳곳에서 진행하는 좋은 프레임워크인데, 오히려 캐싱을 해제해야 하는 상황에서 해제가 안된다는 이상한 점을 발견했다. 그리고 15는 분명 RC 버전인데, 그러면 베타가 아닐 뿐 결국 테스트 환경이라고 생각되는데 문서도 15를 기준으로 작성되어있....나?
어느덧 예정된 노마드코더 북클럽의 마지막 장에 온것 같다. 책은 아직 많은 내용이 남아있긴 하지만, 아마도 여기까지가 Clean Code의 초입이 아닌가 싶다. 남은 내용은 이제 스스로 읽어야 한다고 생각하니 기대 반 걱정 반이다. 그리고 다 읽으면 시간날 때 책장에 고이 모셔둔 TDD도 다시 읽어봐야 겠다.
이번엔 Java의 근간을 이루는 class
에 대한 이야기였다. 어찌보면 단점이라고 볼수도 있는데, Java는 아무리 간단한 일련의 코드라도 클래스 단위로 만들어져야 한다. 1 + 1
을 계산한다 쳐도 class
부터 만들고, 그 안에 main
메서드를 만드는 방식.
JS의 prototype 기반의 OOP는 좀더 자유도가 높은 느낌? Prototype을 상속받아, 해당 prototype이 가진 모든것을 자신의 것처럼 사용할 수 있는 JS의 객체지향은 솔직히 Java의 OOP 이론과는 맞지 않은 부분이 많지 않나 싶다. 반면 class
, new
키워드 (JS에도 있는건 알고 있다), 추상 클래스와 인터페이스, 추상 메서드 등에 대한 이야기는 적당히 몰라도 {}
만 사용해도 객체를 만들 수 있다는 점은 JS의 접근성을 올려주는 부분이지 않을까 싶다. 그래서 여기에서 캡슐화, 추상화 등에 대한 이야기를 한다고 하더라도 완벽히 와닿지는 않을것 같다.
한편 여러가지 복잡한 지식과 규칙이 필요하다는 것은, 그 규칙을 지켰을 때 얻을 수 있는 이점이 여럿 있다는 것이다. Java의 class
는 어떤 객체의 생김세를 정확히 묘사하여 해당 객체가 분명히 가지고 있는 기능들을 묘사한다. 그리고 자신이 가진 것들을 자식 객체에게 전달하고, 자식이 가져선 안되는 것들을 제한하는 등 좀더 복잡한 객체를 정확하게 다루는데 더 유용하지 않은가 생각이 든다.
어쨋든 결국 Java의 클래스와 객체는 함수라고 하는 작은 단위의 코드를 연관성이 높은 이들끼리 모아 관리하는 단위라고 볼 수 있다. 우리의 세상의 많은 도구들과 마찬가지로, 자신의 존재 이유가 명확하게 정의된 단위이다. 그래서 메서드(함수)를 예쁘게 작성하더라도, 그를 담기 위한 클래스를 중구난방으로 만들거나, 서로 연관이 적은 메서드를 모아두는 방식으로 작업하면 Java 코드의 가독성은 저 멀리 하늘 위로 버려지게 될것이다.
하지만 코드의 표현력과 그 코드로 이루어진 함수에 아무리 신경 쓸지라도 좀 더 차원 높은 단계까지 신경 쓰지 않으면 깨끗한 코드를 얻기는 어렵다.
클래스는 객체의 설계도의 역할을 한다. 그 유명한 붕어빵 비유를 생각해보면, 클래스는 붕어빵 틀, 인스턴스는 붕어빵이라고 이야기한다. 그렇다면 생성자는 레시피 정도에 해당하려나? (어디서 "붕어빵틀 비유는 잘못되었다, 붕어빵이라는 개념이 클래스, 만들어진 붕어빵이 인스턴스다"이런 이야기 비슷한걸 본거 같은데, 결국 비슷한 말이 아닐까 싶다)
어쨋든 클래스에는 자신을 이용해 만들어진 인스턴스 즉 객체의 모습이 전부 묘사되어있다. 그러므로 그 데이터를 나타내는 인스턴스 변수, 기능을 나타내는 메서드는 잘 정리되어 있어야 클래스에 대한 가독성이 높을 것이다. Clean Code에서는 이렇게 제안한다.
public static final
)가 제일 처음에 나온다.private static
)가 다음에 나온다.private
)가 다음에 나온다.public
)가 다음에 나온다.마치 설명서를 읽는 느낌이다. 진짜 옛날 레고 설명서도 제일 처음에 재료가 나열되고, 그 뒤에 어떻게 조립하는지, 작은 조립형 부품이 있으면 과정 사이에 끼워서 작성되어 있었다. 코드도 어쨋든 언어이고, 언어는 읽기 쉽게 하기 위한 기법이 다 존재한다는 의미인것 같다.
추상화 단계가 순차적으로 내려간다. 그래서 프로그램은 신문 기사처럼 읽힌다.
여기 3번 과정에서 공개 변수가 필요한 경우는 거의 없다고 하셨는데, 아마 (이전 장에서 얘기한) 자료 구조 이야기는 아니고 진짜 객체를 의미하시는 거겠지?
함수는 물리적인 행 수로 크기를 측정했다. 클래스는 다른 척도를 사용한다. 클래스가 맡은 책임을 센다.
함수와 마찬가지로 클래스는 작을 수록 좋다. 함수(메서드) 여럿이 비슷한 기능끼리 뭉친 것이니, 당연히 한 페이지에 잘 담기는 클래스가 더 좋지 않겠나? 하지만 좀더 나아가서, 클래스 본연의 기능인 책임을 기준으로도 적은것이 중요하다는게 강조된다.
클래스는 어떤 객체가 어떤 기능을 할 수 있는지를 묘사하는 설계도이다. 우리 주변 사물만 바라봐도 클래스가 어떤 모습이어야 할지 감이 조금은 잡힌다.
클래스 이름은 해당 클래스 책임을 기술해야 한다.
우리가 컴퓨터를 사용하려고 한다고 가정하자. 컴퓨터를 사용하기 위해서는 일반적으로 모니터, 키보드, 마우스 등이 있을 것이다. 이때 모니터는 모니터 본연의 기능에 집중하고, 키보드와 마우스 또한 마찬가지다. 모니터는 영상을 보여주고, 키보드는 사용자의 키 입력을 전달, 마우스는 포인터를 움직인다. 모니터, 키보드, 마우스라는 이름을 들었을 때 처음으로 생각하는 기능은 정해져 있다.
물론 요즘은 이들의 기능을 합쳐서 제공하는 경우도 많다. 터치 모니터나 Thinkpad의 빨콩 키보드라던지. 하지만 이들을 클래스로 만들게 되면 그냥 모니터 클래스를 만드는게 아닌 TouchScreen
클래스를 만드는 방향으로 진행하게 될것이다. 이것 자체가 나쁘다고는 생각하지는 않는다. 다만, 우리가 코드를 작성하는 측면에서는 보통 여러 기능을 담당하는 경우 표현하기 어렵거나 지나치게 광범위한 이름이 붙게 된다.
간결한 이름이 떠오르지 않는다면 필경 클래스 크기가 너무 커서 그렇다.
클래스를 설명할 때 만약, 그리고, ~하며, 하지만 등의 부사가 나오게 되면 그 클래스는 보통 많은 책임을 지는 상태일 것이다. 그리고 아마 이름이 복잡하게 정해져 있거나, 아무도 뜻을 알아보기 힘든 고유명사같은 이름을 가지고 있겠지 그래서 사람의 이름의 뜻은 알아보기 힘들다. 보통 사람은 하나의 책임만 가지지 않기 때문에
클래스는 책임, 즉 변경할 이유가 하나여야 한다는 의미다.
OOP의 SOLID 원칙의 S를 담당하는 Single Responsibility Principle이다. SRP는 유명한 얘기니 정리하면 될것 같다. 근데 책의 내용중에 깊은 생각이 드는 부분이 있었어서 이는 따로 여담으로 작성한다.
객체지향을 배우면 대체로 응집도를 높히고 결합도를 줄이는 방향으로 클래스를 만들라는 이야기를 한다. 응집도가 높은 클래스는, 인스턴스 변수 수가 적고, 메서드마다 해당 변수를 최대한 활용한다. 절대적인 기준을 가지고 이야기하면, 모든 변수가 모든 메서드에서 사용되어야 한다는 이야기다. 다만 현실적으로 모든 변수를 사용하는 메서드만 가진 클래스를 만드는건 억지로 하지 않는이상 불가능하다.
일반적으로 이처럼 응집도가 가장 높은 클래스는 가능하지도 바람직하지도 않다.
억지로 높은 응집도를 가진 클래스를 만들려다 보면, 아마도 만물은 원자로 구성되어 있고, 원자는 양성자 중성자 전자로 나눠진다는 식의 설명을 클래스 단위로 하게되지 않을까. 대신 어느정도 타협하면서 응집도를 높히는것 자체는 좋은 행위이다.
응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미기 때문이다.
메서드를 나누다가 보면, 특정 작은 기능을 만들때 필요한 매개변수를 분류하게 된다. 그런데 다시 작은 메서드에서 더 작은 메서드를 만들면서 또다시 그중 일부를 매개변수로 분리하게 될것이다. 그러면 계속해서 더 작은 메서드로 매개변수를 전달하는게 싫증나서, 결국 인스턴스 변수로 전환을 시도할 수 있다.
불행히도 이렇게 하면 클래스가 응집력을 잃는다. 몇몇 함수만 사용하는 인스턴스 변수가 점점 더 늘어나기 때문이다.
대신 해당 매개변수들과 메서드를 분리해서 아예 다른 클래스로 분리하게 되면, 해당 클래스는 응집력이 높은 작은 클래스가 될것이다.
클래스가 응집력을 잃는다면 쪼개라!
깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
추상화와 Open-Closed Principle에 대한 이야기가 나온다. 책에 나오는 Sql
클래스의 추상화에 대한 내용은 매우 인상깊었다. 하나의 유틸리티 클래스를 만들고 그들의 메서드 형태로 동작 시키는게 아닌, Sql
추상 클래스의 추상 메서드로 SQL 생성 메서드를 생성하고, Insert
클래스는 INSERT
문을, Select
클래스는 SELECT
문을 만드는 형태로 클래스를 분리하는 예시였다.
클래스를 이야기할때 하는 이야기 중 하나가, ArrayList
보단 List
를 사용하여라. 특히 매개변수로 전달받을땐 무조건 List
를 사용하여라, 의 논조의 이야기를 하게된다. Java의 인터페이스에 대한 이해가 되었는지를 판단하는 기준이 딱 저 말에서 비롯한다고 생각한다. 왜냐면 보통 메서드로 리스트를 전달하는 경우는 여러개의 같은 타입의 객체를 전달하기 위해서 이기 때문이다. 그리고 각 객체를 살펴보면서 어떤 특정 조건을 만족하는 객체를 찾거나, 집계를 내거나 하는 기능을 만들게 될것이다. 이때 우리에게 중요한건 데이터를 모아두었다라는 진실이지, 배열로 모아두었다, 연결 리스트로 모아두었다는 차이가 중요하지 않기 때문이다. 물론 둘의 구현상의 장단이 존재하는건 알고 있다. 하지만 기능적인 부분에선 둘다 데이터를 모아두었고, 데이터를 순서대로 조회할 수 있다는 것이 중요한 이야기 아닌가? 굳이 그들을 어떻게 구현했는지가 중요한게 아니라는 것이다. 그건 문제 풀때 중요한 이야기이다.
완벽한 예시는 Spring Security에 있다. UserDetailsService
에 대한 이야기이다. Spring Security는 일반적인 인증 과정에 필요한 기능들을 다 만들어 둔 상태이다. 대신 개발자한테 요구하는 건 "사용자 정보를 관리하는 주체"이다. 사용자가 username
이라는 PK를 기준으로 자신이 누구인지 증명하려고 할 때, 해당 username
이 있으면 사용자에 대한 정보를 제공하는 기능을 가진 클래스를 개발자에게 요구한다. 그 요구사항이 바로 인터페이스 UserDetailsService
가 되는 것이다.
그리고 개발자가 언제 그 기능을 만들던 간에 나머지 기능이 동작할 수 있도록 인터페이스 UserDetailsService
를 바탕으로 인증의 구현을 진행한다. Spring Security가 만들어질때는 아직 그 방식이 구현되어 있을 필요가 없다. UserDetailsService
인터페이스의 구현 클래스는 무조건 그 기능을 가지고 있다고 가정할 수 있으니까. 대충 사용자 정보가 돌아왔다 치고 나머지 인증 과정을 미리 만들어 줄 수 있다.
여담으로 Spring Boot를 입문하는 입장에서는 오히려 IService
(이 형식 자체가 이미 책 초미에서 별로라고 하기도 했고) 형태의 interface
를 처음부터 만들어서 인터페이스 기반 주입을 피하는게 났다고 본다. 왜냐면 사실 단순한 CRUD 작업에는 분리할 만큼 알고리즘이 복잡하지 않기 때문에 기계적인 추상화를 하게 되기 때문이다. 내가 다루는 도구가 늘어나고, 구현 방식이 늘어날 때 클래스 자체를 갈아끼워야 하는 상황에서 추상화라는 기술이 어떻게 작용하는지를 좀더 정확히 이해하게 되지 않을까.
Java 개발자라면 class와 떨어질 수 없다. 그리고 클래스는 생각보다 더 많은 방식으로 사용할 수 있다. 단순히 비슷한 기능의 코드의 모임이 아니다. 클래스를 어떻게 작성하는지에 따라 유기적인 생물체와 같은 프로그램을 만드느냐, 아니면 맛있는 스파게티 같은 프로그램을 만드느냐가 갈리지 않을까 싶다.
SOLID를 공부하고, 무슨 의미인지 잘 이해하려고 노력해보자. 그리고 시간이 된다면 GOF Design Pattern도 공부해보자. 추상화의 정수를 느낄 수 있을것이다.
원래 여기에 개발자와 대화의 관계에 대해서 열심히 써둔 글이 있었는데......너무 중2감성이라 다른 임시글에 옮겨두었다....
+ 인증샷