개발자로서 1년 가까이 일하면서, 나름 어떤 게 올바른 코드스타일과 아키텍처인지 고민을 많이 했다. 사수가 없는 상황에서 내 코딩스타일에 대한 직접적인 피드백을 받을 수 없었다. 그래서 여러가지 매체를 통해서 스스로 피드백을 형성하려고 노력했다. 그러한 과정 속에서 많이 참고가 됐던 게 포프님의 영상들이었다. 포프님 영상이 좋은 점이, 기술에 대한 CTO급 사람의 솔직한 생각들을 엿볼 수가 있다는 부분이다. 가끔은 너무 극단적이라는 생각도 들지만, 그럼에도 개발자로서 좋은 인사이트들을 얻을 수가 있었다. 이 파트를 쓰는 것도, 포프님의 영상 중 하나인 "주객전도 아키텍처" 를 보고 내가 생각한 내용들을 끄적여보는 거다.
인터페이스는 객체지향적 세상에서 다형성을 획득하기 위해 가장 많이 쓰이는 수단이다. 잘만 활용하면, 유지보수 쉽고, 유연하며, 확장성 있는 어플리케이션을 설계하는 데 큰 도움을 준다. 하지만 실제 많은 코드베이스에서 인터페이스의 이런 장점들을 활용하지 않은 채 의미없이 쓰여지는 경우들이 많다. (인터페이스 하나에 구현체 딸랑 하나 있는 경우들) 물론, 내가 경험이 적은 편이지만, 그래도 레거시 코드들을 유지보수할 일이 몇 번 있었는데 왜 인터페이스를 만들었지?? 하고 물음표가 뜨는 경험들이 적지 않은 편이다.
제일 많이 보게 되는 경우가, DAO 또는 Service Interface 들이다. 거의 틀에 박혔나 싶을 정도로, 무.적.권 Interface를 통해서, 객체참조를 하고 있었다. 처음에 코드를 보고 들은 생각은, 아마 사수가 인터페이스를 먼저 만들어놓고, 후임한테 시켰나 보다 했었지만, (실제로 큰 프로젝트에는 이런 식으로 먼저 위에서 기능정의를 다 끝낸다음 들어온다고 들었다.. ) 조금 더 경험이 쌓인 후에 그렇지 않은 상황에서도 개발자들이 관습적으로 요런 식으로 코딩을 한다는 것을 깨달았다. 아마 이 부분은 내가 JAVA + Spring 베이스의 개발자로 주로 일했던 환경이 영향을 크게 주는 것 같다.
Spring에서 인터페이스는 단순히 다형성을 획득하기 위한 수단 뿐 아니라, RunTime 시 프록시 객체를 얻기 위한 수단으로서도 작용하기 때문에, Spring 의 여러가지 기능들을 활용하려면 인터페이스를 정의해줘야 한다. (물론 프록시 객체를 만드는 데 있어서 꼭 인터페이스를 이용할 필요는 없다. 상속을 활용한 다이나믹 프록시도 있으니까) 물론 코드를 봤을 시, 그런 부분을 고려해서 인터페이스를 정의했다고는 생각이 들지 않는다. 그러면 왜 이렇게 짰을까? 나중에 변경되거나 추가될 가능성을 염두에 두고? 누군가는 그러한 구조가 필요한 이유는 유닛테스트를 하기 위해서라고 말한다.
이제 테스트코드에 대해 한 번 이야기해보자. 나는 큰 프로젝트를 맡아본 경험이 없다. 전에 일했던 곳은 조그마한 규모의 외주 SI 업체였고, 이번에 일하는 곳 역시 작은 규모의 스타트업(하지만 이제 곧 공공데이터 외주를 곁들일 수 있는)이다. 아마 나랑 비슷한 환경에서 일해본 분이라면, 대부분 동감하겠지만, 이런 곳에서는 테스트 코드에 대한 문화가 없다. 당연히 내가 본 레거시 프로젝트도 테스트코드가 전무했다. 그래서 위에서 말한 단위 테스트하고도 무관하고, 누군가 덧붙이기 위한 API로 제공해주는 것도 아니니, 그냥 단순히 관습적으로 만들었다고 밖에 표현할 길이 없다. 그렇다면 만약 독립적인 단위 테스트를 작성한다고 한다면, 인터페이스를 매번 만들어야 할까. 아니 꼭 단위테스트가 필요한 것일까?
TDD나, BDD에 대한 이야기들을 많이 들었다. 실제 개발일을 하다보면 꼭 한 번쯤 듣게 되는 여러가지 키워드들 중에 하나이다. 테스트 코드를 먼저 만들고, 당연히 실행하면 실패로 뜨겠지.. 그 다음 그 테스트 코드를 성공시키기 위한 절차를 수행하라. 일종의 개발방법론에 관한 이야기로 나는 해석했다. 큰 프로젝트나 어떠한 민감한 프로세스에 대한 개발방법론이라면 유용할 수 있겠지만, 대부분의 경우에는 리소스 낭비라고 생각한다. (내 블로그니 편하게 그냥 쓰겠다.)
나는 단위 테스트가 꼭 필요하다고 생각하지 않는다. 사실 내가 일했던 경험..(사실 별 큰 경험은 없으므로, 뭔가 자신있게 얘기는 못하지만.) 안에서는 테스트 코드를 작성하는 피로감만 더 들 뿐, 이걸로 인해, 소프트웨어의 신뢰성과 안정감이, 내가 쏟은 리소스에 비해 크게 상승했다는 느낌을 가지지 못했다. 어떤 민감한 프로세스.. 예를 들어 결제모듈 같은 몇몇 경우를 제외하고는 대부분 통합테스트만으로도 충분하다고 생각한다.
테스트 커버리지 100% 달성한다와 같은 목표는 사실 공부목적이면 나쁘지 않다고 생각한다. 어쨌든 그러한 목표를 달성하기 위해서라면, 테스트를 어떻게 쉽게 짤 수 있을까 고민하게 되고, 그러한 고민 과정 속에서 소프트웨어를 POJO (자바 기준) 기반으로 만드는 방향으로 접어들 수 있다고 생각한다. 실제로 나도 테스트 코드를 짜다가, 짜기가 힘들어서 이걸 어떻게 짜기 쉽게 만들지에 대해 고민을 하면서, 구조에 대해서 다시 한 번 생각하게끔 되는 경험이 되었다.
그러한 교육목적을 제외한다면, 굉장히 리소스낭비인 목표이며 단위테스트를 실행시키기 위해, 반드시 인터페이스를 사용할 수 밖에 없는 환경이라면, 그것 자체가 조금 잘못된 설계일 수도 있다는 게 내 생각이다. 다시 한 번 돌아가보자. 단위 테스트를 위해 인터페이스를 쓴다는 것은 무엇을 의미하는가. 그 말은 내가 테스트하고 싶은 모듈이 다른 모듈과의 의존관계를 갖고 있어서, 그 참조하는 모듈을 모킹한 다른 모듈을 만들어내지 않으면, 그 모듈만 독립적으로 테스트하기 힘들다는 의미를 갖고 있다. 그래서 인터페이스를 통해 참조함으로서 테스트용 Mocking 모듈을 만들어내서 주입할 수 있어야 한다는 의미이고. 특히 테스트할 모듈이 DB와 같은 외부 인프라와 연동되어있으면, 실제 인프라로 테스트할 수는 없는 노릇 아닌가.
포프님의 영상에서는, 단위 테스트에도 4가지 레벨이 있다고 한다.
1. 함수 안에서 모든 게 끝나는 테스트
2. 객체에 한정된 테스트
3. 모듈간의 디펜더시가 있는 테스트
4. 그 이상의 무언가?
1,2번 같은 케이스는 사실 단위테스트하기 쉽다. 함수야 인풋을 집어넣으면 아웃풋이 동일하게 나오는지만 체크하면 되는 것이고, 객체도 그 객체에 대한 상태 변경만 체크하면 되는 거 아닌가. 문제는 3번 이상부터인데, 어떤 테스트 대상 모듈이 다른 제3의 모듈과 디펜더시가 걸려있는 상황에서는, Mocking을 제외하고도 테스트할 타깃 모듈의 함수 자체가 다른 모듈을 직접 받는 게 아니라, 그 모듈에서 나오는 데이터만 받도록 바꿀 수 있다면, 데이터만 준비된다면, Mocking은 필요없어진다. 우리가 테스트할 것이 모든 함수가 아니라, 어떤 중요함수만이라면 실제 Mocking 객체가 필요한 함수는 분리해서 포함시키도록 짤 수도 있는 셈이다.
이렇게 되면, Test 과정에서 SliceTest 같이 부분적으로 Bean들을 띄워놓을 필요도 없이, 생성자에서 주입받는다고 치더라도 Null로 주입하고, POJO 기반으로 짤 수 있는 틀을 제공해주는 것이다. 이렇게도 안 된다면, 통합테스트로 넘어가면 된다. 포프님 영상에서 특히 공감했던 부분은 테스트코드란, 개발자가 이 코드는 이렇게 동작할 것이라는 가정을 확인시키기 위한 용도일 뿐이지, 개발자가 예상치 못한 경우의 수를 찾아내주지는 않는다고.
하 글을 쓰다보니 시간이 길어졌다.. 사실 떠오르는 생각은 많다. 이렇게 인터페이스가 무분별하게 남용됨으로서 겪게되는 실.. 그리고 단일책임을 넘어 지나치게 책임이 낮게 설정된 오브젝트는 혹시 인터페이스를 남용함으로서 잘못 배워서 그런 거지 아닐까.. (닭이 먼저인지 달걀이 먼저인지는 모르겠지만..)
그리고 코드에 대해서 평소 생각했던 내용들, 밑에서 이어가서 적고 싶지만.. 생각이 조금 더 정리가 되면 계속 글을 적어나가겠다. 일단 키워드만 적고...
다시 글을 써보겠다.. 이렇게 시간이 나면 틈틈히 생각들을 적어보자.. 이번에는 자바의 어노테이션에 관한 생각이다. 조금 더 폭넓게 생각해보자면, 메타프로그래밍에 대한 이야기일 수 있겠다. 자바를 기준으로 얘기를 해보자면, 어노테이션이 대표적으로 떠오른다. 어노테이션은 무엇인가. 간단히 말하면 코드에 대한 주석이다. 처음 만든 사람이 무슨 목적으로 만들었는지 모르겠지만, 사실 어노테이션은 주석과 달리 정책에 따라 컴파일 시점까지 유지할지, 런타임 시점까지 유지할 수 있을지 선택할 수 있단 것 말고는 그 자체로 전혀 기능이 없다. 근본적으로는 주석과 다름이 없는 기능.. 그리고 누군가는 그런 생각을 했을 것이다. 이 코드에 대한 주석(메타 코드)를 바탕으로 무언가를 또 다시 할 수 있지 않을까?
내가 스프링 프레임워크를 처음 접했을 때가 기억난다. 당시 나는 웹개발을 공부하던 학생이었고, PHP를 통해서 웹 개발을 공부하다 자바로 넘어가서 JSP, Servlet, Tomcat, Maven 과 같은 도구로 개발공부를 하고 있었다. 당시 나는 스프링프레임워크를 전혀 모르고 있던 상태이고, 자바에서 어노테이션으로 무언가를 할 수 있을 거라는 생각조차 없었다. 그러다가 부트를 통해 처음 스프링 프레임워크를 접했는데, 내게 있어서 가장 충격이었던 점은 메서드에 @RequestMapping이라는 어노테이션만 달면 자동으로, 서블릿 컨테이너가 Http Request를 그에 맞춰서 맵핑해준다는 점이었다. 당시 내게 있어서 마법같은 기능이었고, 도대체 어떻게 동작하는지 한동안 고민하곤 했다.
그 외에도 충격적인 기능들이 많았는데, 스프링부트에서 제공해주는 어노테이션들을 활용하면, 어노테이션 하나만 달면, 내가 그 전에 쌩으로 짜고 실행해야했던 수많은 로직들을 자동으로 처리해주는 것을 보고, 정말로 편리하다는 생각이 들었다. Lombok을 처음 접했을 때도 얼마나 충격적이었나. 자바에서 필연적으로 많아지는 수많은 보일러플레이트 코드들을 어노테이션 하나로 대체하고, 개발자는 비즈니스 로직에만 집중할 수 있단 것이 엄청난 장점처럼 보였다. 그래서 한동안 리플렉션 API 에 대해서 공부를 하면서, 메타 프로그래밍에 공을 들였던 적이 있다.
그동안 자바를 공부하면서 느껴보지 못했던 신선함이며 내가 자바를 좋아하게 된 계기이기도 하다. 물론 다른 언어에도 이런 기능을 제공해주고 있겠지만, 당시에 나는 몰랐으니까.. 자바의 리플렉션 기능은 아주 강력하다. Class 객체가 주어진다면, 아니 그 이름만 알고 있더라도, 메타 데이터를 통해 접근해서, 그 클래스의 생성자, 메서드, 필드 인스턴스를 가져오고 조작할 수 있으며 심지어 컴파일 당시에 존재하지 않던 클래스도 이용할 수 있다.
그러나 이펙티브 자바나 여러가지 매체에서 경고하다시피 적극적으로 쓰면 쓸수록 단점들이 드러나기 마련이다. 뭐든 과해지면 오바다. 다음은 토비님이 발표한 [Oracle Code Seoul 2017] Java 9과 Spring 5로 바라보는 Java의 변화와 도전 에서 어노테이션과 리플렉션을 활용한 메타프로그래밍의 단점들이다.
컴파일 타입 검사가 주는 이점을 누릴 수 없다.
어노테이션은 어떤 타입 정보가 아니다. 단지 메타 데이터일 뿐이지, 어떤 특정 행위를 규정하지 않는다. 행위는 이 어노테이션을 사용한 프레임웤이나 라이브러리 내부에 정의된 코드를 통해 동작한다. 그래서 타입을 기반으로 코드가 올바르게 동작할 거 라는 것을 검증할 수 없다.
리플렉션을 이용하면 코드가 지저분하고 장황해진다.
내가 메타 프로그래밍에 관심을 가졌던 점은, 어노테이션으로 뒷단의 로직들은 관례로 감추고 개발자는 비즈니스 로직에만 집중할 수 있게 해주는 점이었다. 하지만 조금 다른 식으로 말하자면 이 메타정보를 가지고 코드를 조작하는 또 다른 장황하고 긴 로직이 프레임워크나 라이브러리 내부에 감춰져있다는 점을 의미한다..
이해하기 어렵다
리플렉션에 대한 개념이 없으면 코드를 이해하기 어렵게 만든다. (설사 알고 있더라도..) 일단 자바의 기본 캡슐화 은닉성 같은 것들 다 쌩깔 수 있으며, 이로 인해, 원래는 동작하지 않아야하는 로직들을 수행할 수 있게 되고, 이는 이해하기 어렵고 오해하기 쉬운 코드를 양성하게 된다. 어노테이션 자체만으로 코드가 어떻게 돌아가는지 확인할 방법이 없다. 내가 스프링부트를 처음 배웠을 때도 이와 같은 감정이었는데, 부트는 어노테이션과 리플렉션을 활용한 메타 프로그래밍으로 범벅이 되어있다. 처음 배우면 당췌 코드가 어떻게 돌아가는지 확인할 방법이 없다. 이 어노테이션을 붙이면 이런 기능을 한다는데 그래서 어떻게? 또한 어노테이션끼리 확장을 했을 때, 얘가 어떻게 동작할지 클래스나 인터페이스처럼 확신할 수가 없다..
성능이 떨어진다.
당근 런타임 상에서 동적으로 타겟팅이 결정되는 리플렉션 api는 성능상 후질 수 밖에 없다. 컴파일 시 코드를 생성하는 lombok과 같은 어노테이션 프로세서를 활용하는 것은 그렇진 않겠지만..
테스트가 어렵다.
어노테이션은 그 자체로 동작하는 코드가 아니다. 어노테이션을 활용하여 어떤 부가적인 기능을 수행하는 메타 프레임웤 위에 올려야지만 동작한다. 흔히 사용하는 @RequestMapping 같은 것도, 그 자체로 테스트할 수 가 없다. 스프링 컨텍스트를 통째로 올리고, mock 오브젝트를 쓰든지 뭐지 해서 호출해봐야지만 정상작동하는지 확인할 수 있다.
요런 이유들로 테스트가 쉽게 가능하고 예측할 수 있으며, 자유롭게 확장가능한, POJO 기반으로 코드를 짜는 걸로 트렌드가 돌아오는 중이고..하지만 알다시피 자바는 그러기에 타이핑양이 너무 많다.. 그래서 모던자바(자바8이후) 부터는 함수형 프로그래밍이 부분적으로 가능해지므로, 이런 코드량을 줄여주는 Plain Old Functional Object를 적극 활용하자는 게 지금 스프링이 대비하고 있는 미래의 모습이라는 게 영상의 주요요지이다..
잘 짠 코드란 무엇인가..모든 사람마다 정의는 다 다르겄다. 나는 직접 구현은 잘 못하는 편이다. 알고리즘도 잘 하는 편이 아니고, 보통 머리 수준이다. 복잡하게 짜여진 알고리즘, 번뜩이고 창의적인 문제풀이능력은 나에게 있어서 너무 힘들다. 하지만 내게 있어서 일해보면서 가장 크게 와 닿는 잘 짠 코드란, 가독성이 좋은 코드다. 왜 어떤 코드는 가독성이 안 좋고, 어떤 코드는 가독성이 좋은가. 내가 생각하기에 잘 하는 개발자는 코드를 선언형으로 잘 짠다.
그러니까 실제 구현체는 내부적으로 감추고 그걸 추론하기 쉽게 추상화해서 코드를 잘 짠다. 이거는 함수형이든 객체지향이든 상관없이 프로그래밍 패러다임 자체가 점점 더 인간중심적으로 친화적으로 가는 것이 더 좋다고 알려주는 것 같다. 사람은 무언가를 한다고 할 때, 기계처럼 생각하지 않는다. 그러니까, 여행을 간다라고 했을 때, 비행기는 몇 시에 어떤 표를 끊고, 공항까지는 택시를 탈 거고 카카오 택시로 부를거고, 요런 과정을 그리면서 생각하지 않는다는 것이다.
.. 뭔가 더 하고 싶은 말이 있는데 조금 더 정리해서 붙이겠다..
개인적인 생각으로 모든 상황에서 최고의 코드스타일은 존재하지 않는다고 생각한다. 자기 스타일별, 또한 주어진 환경 속에서 최선을 도모할 뿐. 어떤 상황 속에서는 성능 최적화를 위해 어느정도 가독성을 포기해야 되는 경우가 생길 수 도 있다. 결국 밸런스의 문제다. 하지만 어떠한 상황에서도 안 좋은 코드는 존재한다고 생각한다. 최소한 기본은 지켜야 하는 코드스타일의 정석이 존재한다면 아래와 같다고 생각한다.
일관된 네이밍 컨벤션
되도록이면 두루두루 알려진 대중적인 네이밍컨벤션을 사용하도록 하자. 카멜케이스든 케밥케이스든 스네이크케이스든 간에 뭐를 선택하든 상관없다. 다만 그 프로젝트 내에서는 해당 컨벤션에 대한 일관성을 유지하자. 변수는 되도록이면 명사로, 함수는 되도록이면 동사로 처리하도록 하자
모듈의 응집도는 높게, 결합도는 약하게
유명한 말이다. 내가 제일 중요하다고 생각하는 부분이며 어떻게 보면 OOP의 핵심철학이라고 생각하기도 한다. 어느 한 모듈을 수정했을 시 다른 모듈이 영향을 끼치는 범위를 최소화해야 한다. OOP를 예로 들자면 프로그래밍 세계를 서로 자율적인 객체들끼리 메시지를 통해 통신하는 방식이라고 하는 것처럼, 어느 한 객체가 다른 객체를 전적으로 다루면서 다른 객체는 수동적으로 다뤄지는 게 아니라, 메시지만 던지고 메시지를 받는 객체는 지가 알아서 자율적으로 내부로직을 수행하도록 짜는 것처럼. 모듈 간의 결합도를 약하게 가져가야 한다. 객체들끼리는 다른 객체가 뭘 하든 서로 관심없이 지 관심사만 집중하는 개인주의자로 만들어야 한다. 상속을 포기하는 이유나 private 쓰는 이유나 인터페이스 쓰는 이유들도 다 여기에 해당된다고 볼 수 있다.
책임을 쪼개서 할당하자
모듈의 응집도는 높게, 결합도는 약하게 가져가기 위해서는 관심사를 쪼개야 한다. 작게는 함수부터, 클래스, 패키지, 프로젝트 까지, 쪼개서 분리해야 된다. 그렇게 해서, 코드의 재활용성도 높이고, 가독성도 높일 수 있다.
중복된 코드들이 여러 개 보인다면 재활용을 고민해보자
코드 조각이면 함수로 빼놓거나, 같은 함수도 여러 개 중복된다면 커리함수나 프록시 패턴처럼 AOP 패러다임을 따라 따로 처리할 수 있는 패턴을 고민해보자. 중복된 코드가 아니더라도 어떤 로직을 수행하는 하나의 코드조각이 너무 길다면 가독성을 위해 함수로 빼놓는 것도 고려할 수 있다.
확장성을 염두에 두자
항상 시나리오가 교체될 가능성을 염두에 두고, 너무 코드를 딱딱하게? 작성하지 말고 어느정도 유연함을 가미해야 된다. 인터페이스를 쓰는 가장 큰 이유도 여기에 있다. 결국 로우레벨보다는 좀 더 추상화된 레이어를 하나 두고 그 레이어를 통해 통신해야 이런 게 편하다. 하드코딩도 강한 의존성 예시의 하나다. 가끔 하드코딩해도 괜찮지 않을까 하는 경우가 있올 수도 있는데, 왠만하면 변수로 빼놓거나, 환경변수로 빼놓는 게 변화에 유리하다.
직관적이어야 한다.
위의 팁과는 좀 상반된 의견이긴 한데, 너무 추상화하다보면 코드가 직관적이지 않고, 애매모호해지기도 한다. 결국 밸런스의 문제다.
인덴트를 줄이자.
중첩 if 문, 중첩 for문 등.. 중첩으로 2중 3중.. 11중 depth가 늘어나는 구조가 보이면, 줄일 수 없을 지를 고민해보자. 만약 if문이 너무 많다면, Guard clasue나 다형성으로 대체할 수 있을지 고민해보자. 이거 은근히 진짜로 중요하다.
파라미터는 최소한도로
파라미터가 많으면 줄일 수 없는 지를 고민해보자. 함수를 분리하거나, 객체로 묶어서 던지거나, 맴버변수로 빼놓거나 등등. 3개 이하가 적정선인 것 같다.
의미있는 이름과 타입을 사용하자.
코드조각들은 하나하나 의미가 있어야한다. 타입도 마찬가지인데 DTO를 따로 만드는 이유도 여기에 있다. 당장 편하다고 HashMap 같은 거로 통짜로 인자를 주고받는 코드는 나중에 유지보수를 최악으로 만들어준다.
메모리를 생각하자.
웹 개발 같이 추상화된 분야를 개발하면서 메모리 성능에 대해서 너무 신경쓰면서 코딩을 할 필요도 없지만, 어느정도는 항상 인지를 하고 개발을 해야 된다. 너무 IO작업, 네트워크 콜, DB 쿼리 횟수 같은 거로 성능개선을 도모하다가 인메모리 관리를 등한시하다가는 골로 간다.
왠만하면 불변을 디펄트
나중에 불편하면 가변으로 바꾸다곤 하더라도, 디펄트로 불변이면 코드안정성이 늘어난다.
동시성 이슈를 염두해두자
비동기 프로그래밍 섞이면, 결국엔 동시성 이슈에 맞닥뜨리게 된다. 항상 코드짤때 염두에 둬야 한다.
모듈을 쪼갠다고 너무너무 쪼개면 난처하다.
MSA를 예로 들자면, 서버가 하는 일을 너무 쪼개다보면, 서버 하나당 단순 CRUD 기능들의 조합체가 될 수 있다. 클래스나 함수 같이 작은 단위도 마찬가지. 너무 쪼개면 더 이상 의미없는 수준에 이른다. 결국 밸런스의 문제다.
예외처리를 항상 신경쓰자
예외가 일어날 수 없는 방향으로 코드를 짜자, 예를 들어 전통적인 for문 썼을 시 index out of error 가 발생할 수 있으므로, stream foreach로 돌리거나 iterator로 체크해서 있으면 돌리는 형식이나, switch문에서 default 키워드 안 써서, 이상한 데로 빠질 수 없도록 enum 값으로 조건을 단다거나 등등..
만약 내가 컨트롤할 수 없는 예외가 발생할 경우가 있다면 예외처리 로직을 짜주자. 할 거 없다면 에러로그라도 띄우자.
(객체 지향 프로그래밍 - 1조 달러의 재난) 이라는 아티클을 보고 인터페이스를 걷어내자는 내 생각과 연결이 되면서 생각이 많아져서 쓰는 글이다..
객체지향이 처음 등장한 이유는 무엇일까.. 아마도 그것은 이해하기 힘든 기계론적 논리구조에서 좀 더 인간친화적으로 코드를 바라볼 수 있게 추상화해주기 위해, 복잡성을 관리하기 위해, 등장한 패러다임일 것이다.. 아티클에서는 객체지향의 원래 목적은 독립적인 프로그램(세포)들이 서로 메시지를 보냄으로서 정보를 전달하는 것이 원래 핵심의도라고 말한다. 음.. 원래 내가 알던 OOP의 핵심 개념하고 쪼끔 다른 이야기이긴 하지만, 뭐 원래 의도가 그랬다면야..
아무튼 내가 이제까지 생각한 OOP의 의도는 개별적이고 복잡한 수많은 코드조각들을 추상화를 통해서 뭉뚱그려서 바라볼 수 있게 해주면서 단순화시키는 게 핵심이라고 생각했다. 그 과정에서 상속을 통한 코드 재사용과 다형성 획득 같은 부가적인 개념들이 들어가게 되는 거고.. 아무튼 그렇게 되면서, 점점 객체지향의 지상목표같은 것이 되어버리는 게 하나 있는 것 같다.
대표적으로 자바같이 태생부터 OOP와 연관된 진영에서는 모든걸 추상화 하려고 시도한다. 특정 기술이나 구현에 종속되는 걸 병적으로 싫어해서 모든 걸 인터페이스를 통해서 표준화시킬려고 시도한다. 물론 많은 사람들이 쓰는 프레임워크나, 라이브러리에서 이런 시도는 필요하겠지만.
근데 오히려 이런 트렌드를 따라해서 우리의 개인프로젝트에도 적용시키는게 맞는 것인가>
과한 추상화..
지나친 추상화는 도리어 코드의 실제 의도를 저 밑바닥으로 감추고, 당췌 코드가 뭘 의도하는 건지 파악하기 힘 들게 한다. 원래 객체지향에서 목적이었던 것 중 하나가 객체의 재사용성 아닌가.. 근데 오히려 객체가 상태를 가짐으로서, 실제 재사용하기는 더 까다로워지...
OOP에서는 객체 하나하나를 자율적인 존재로 생각하고 다른 객체들에게 인터페이스만 노출시켜서 서로 메시지를 주고받으며 생명력을 얻는.. 그럼으로서 객체 하나가 다른 객체에 전적으로 의존하는 상황을 피하고, 메시지를 주고받으며 메시지를 통해서 객체 스스로 행동하게끔..
이 부분은 조금 더 생각을 정리해봐야겠다... 일단,
함수형에서 추론을 많이 활용하는데 IDE 입장에서는 명시적으로 해주는 것이 컴파일 시점에 더 강한 안정성을 부여할 수 있게 기능을 도와주는 것.
요새 관심을 가지는 책인 Anti SQL도 그렇고, 뭐든지 과한 것이 문제다. 문제를 쉽게 할려고 하는 패러다임이 과해져서 오히려 문제를 더 복잡하게 만드는... 이거 나중에 생각정리되면 써야겠다..
https://www.youtube.com/watch?v=BFjrmj4p3_Y&ab_channel=OracleKorea
https://mono9.tistory.com/3
포프님 유튜브를 보고 이런저런 생각이 들어 서칭을 하다가 들어오게 되었습니다! 저는 학부생이고 스프링부트를 공부하고 있습니다. 기존에 사용하던 레이어드 아키텍처에서 요즘 흔히 유행한다는 헥사고날,클린 아키텍처로의 변환을 공부하고 있는데요. 두 아키텍처 모두 결국 목표로 하는것은 클래스A가 클래스B를 의존할 때, 클래스B의 변화때문에 클래스A의 변경을 막고자 클래스A가 인터페이스B를 의존토록 하고, 클래스B가 인터페이스B를 구현하도록 하는것이 핵심이라고 생각했습니다. 그런데 포프님 유튜브를 보면 실무에서 막상 요구사항 변경이 일어나면 구현체만 변경해도 되는게 아니라 인터페이스까지 변경해야할 상황이 훨씬 잦다고 말씀하셔서 헥사고날,클린 아키텍처에 대한 의문이 들고 있습니다. 저는 아직 학생이라 여러 상황들을 접해보지 못했는데, 현업에 계신 입장에서, 인터페이스의 남용에 대해 고민하고 계신 입장에서 클린,헥사고날 아키텍처들에 대해 어떻게 생각하시는지 궁금합니다.
개인적으로 인터페이스는 미래의 나 또는 미래의 이 코드를 수정해야 하는 사람을 위해 작성하게 된다고 생각합니다. 지금 개발 할 때는 인터페이스를 구현한 구현체가 하나밖에 없더라도 세상일 어떻게 될지 모르니 귀찮고 복잡하더라도, 인터페이스를 구현해두는게 쉐도우 복싱이더라도, 하는게 좋지 않을 까요?