//
Search
Duplicate
📖

[Clean Code] 12장 시스템 (2)

상태
클린코드
담당자
시작일
2022/05/01 17:35
최종편집일
2023/12/10 11:43
확장
'처음부터 올바르게' 시스템을 만들 수 있다는 믿음은 미신이다.
깨끗한 코드는 코드 수준에서 시스템을 조정하고 확장하기 쉽게 만든다.
소프트웨어 시스템은 물리적인 시스템과 다르다. 관심사를 적절히 분리해 관리한다면 소프트웨어 아키텍처는 점진적으로 발전할 수 있다.
횡단의 관심사
원론적으로 모듈화 되고 캡슐화된 방식으로 영속성 방식을 구상할 수 있다. 하지만 현실적으로는 영속성 방식을 구현한 코드가 온갖 객체로 흩어진다. 여기서 횡단의 관심사라는 용어가 나온다. 사실 EJB 아키텍처가 영속성, 보안, 트랜잭션을 처리하는 방식은 관점 지향 프로그래밍 AOP(Aspect-Oriented Programming)을 예견했다고 보인다.
AOP에서 관점(Aspect)이라는 모듈 구성 개념은 "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다"라고 명시한다.
여기서 영속성?이 무엇을 뜻하는지 좀 와닿지 않았다. 그리고 무엇보다도 글들이 어렵게 느껴졌다.
이를 해결하기 위해서, 영속성과 AOP를 찾았다.
영속성 이란?
출처 네이버 영한사전
없어지지 않고 오랫동안 지속됨을 뜻한다.
일단은 '영속성'이란 일련의 과정들이 없어지지 않고 오래동안 지속되고 있다고 생각하면서, 책에서 말하는 '자바에서 사용하는 관점 혹은 관점과 유사한 메커니즘을 세 개'를 살펴보자.
1.
자바 프록시
2.
순수 자바 AOP 프레임워크
3.
AspectJ 관점
자바 프록시
프록시란 '대리'라는 의미로 프록시에게 어떤 일을 대신시키는 것이다.
개별 객체나 클래스에서 메서드 호출을 감싸는 경우가 좋은 예다.
프록시 패턴을 쓰는 이유는 접근을 제어하고 싶거나, 부가 기능을 추가하고 싶을 때 사용한다. 아래 예시 코드를 보자
책의 예제는 어렵다고 생각한다. 아래의 예제를 보고 책의 예제를 살펴보도록 하자.
package com.programmers.java.proxyPattern; public interface EventService { void createEvent(); void pushEvent(); }
Java
복사
package com.programmers.java.proxyPattern; public class UseEventService implements EventService { @Override public String createEvent() { System.out.println("create event"); } @Override public String pushEvent() { System.out.println("push event"); } }
Java
복사
package com.programmers.java.proxyPattern; public class Proxy implements UseEventService { private UseEventService useEventService; public Proxy(UseEventService useEventService) { this.useEventService = useEventService; } @Override public String createEvent() { long begin = getSystemTimeMills();// 새로 추가한 기능 useEventService.createEvent();// 부모 객체의 기능 System.out.println(endSystemTimeMills(begin));// 새로 추가한 기능 } @Override public String pushEvent() { long begin = getSystemTimeMills();// 새로 추가한 기능 System.out.println("push event");// 부모 객체의 기능 System.out.println(endSystemTimeMills(begin));// 새로 추가한 기능 } long getSystemTimeMills() { return System.currentTimeMillis(); } long endSystemTimeMills(long begin) { return System.currentTimeMillis()-begin; } }
Java
복사
package com.programmers.java.proxyPattern; public class Main { public static void main(String[] args) { // UseEventService 클래스의 메소드를 호출하는것이아닌 프록시클래스의 메소드를 호출한다. EventService eventProxyHandler = new Proxy(new UseEventService()); System.out.println(eventProxyHandler.createEvent());// 내부적으로 createEvent 메소드를 호출한다. } }
Java
복사
장점
위에 코드를 보면 알 수 있듯이, 우리는 기존의 service에 직접적인 추가적인 기능을 적용하지 않는다.
하지만 부수적인 기능(보안, 트렌젝션 등등)은 프록시를 통해서 작업하기 때문에 클래스의 근본이 되는 기능들은 내부에 설계를하고, 부수적인 기능은 프록시를 적용해 작업을 하면 깨끗한 코드를 작성할 수 있다.
단점
하지만 설계를 하면서 큰 문제를 느꼈을 것이다.
코드가 상당히 많으며 제법 복잡하다.
코드의 '양'과 크기는 프록시의 두 가지 단점이다.
'다시 말해서, 프록시를 사용하면 깨끗한 코드를 작성하기 어렵다! 또한 프록시는 시스템 단위로 실행 '지점'을 명시하는 메커니즘도 제공하지 않는다.'
순수 자바 AOP 프레임워크
AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그램이라고 불린다. 아래 그림을 보자
위 그림을 보면 알 수 있듯이 횡단 관심사항의 기능을 모듈화 하여 중복을 최소화시키면서, 핵심 기능에 집중하도록 한다는 것을 알 수 있다. 위예 그림만 보면 이해가 잘 안 된다고 생각한다. 나 또한 그랬으니까. 아래 코드 예제를 보자
스프링 에노테이션 기반의 AOP를 살펴보자.
@Component @Aspect public class PerfAspect { @Around("execution(* com.eventTest..*.EventService.*(..))") public Object logPerf(ProceedingJoinPoint pjp) throws Throwable { long begin = System.currentTimeMillis(); Object resultValue = pjp.proceed(); System.out.println(System.currentTimeMillis() - begin); return resultValue; } }
Java
복사
Component : 스프링 빈으로 등록한다.
Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것
Around 어노테이션은 타깃 메서드를 감 써서 특정 Advice를 실행한다는 의미이다.
execution : execution(* com.event..*.UseEventService.*(..)) 의 의미는 com.event아래의 패키지 경로로 EventService 객체의 모든 메서드에 Aspect를 적용한다는 의미이다.
Advice : 실질적으로 어떤 일을 해야 할 지에 대한 것 ==> 현재는 메서드가 실행된 시간을 측정하기 위한
package com.programmers.java.proxyPattern; public interface EventService { void createEvent(); void pushEvent(); }
Java
복사
public class UseEventService implements EventService { @Override public String createEvent() { try { Thread.sleep(1003); } catch(InterruptedException e) { e.printStackTrace(); } System.out.println("create event"); } @Override public String pushEvent() { try { Thread.sleep(1000); } catch(InterruptedException e) { e.printStackTrace(); } System.out.println("push event"); } }
Java
복사
코드를 실행해보자
@Component public class AppRunner implements ApplicationRunner { @Autowired UseEventService useEventService; @Override public void run(ApplicationArguments args) throws Exception { useEventService.createEvent(); useEventService.pushEvent(); } }
Java
복사
위 코드가 java에서 프록시로 구현할 때 보다 훨씬 더 깨끗하다. 일부 상세한 정보는 애너테이션에 포함되어 그대로 남아 있지만, 모든 정보가 애너테이션 속에 있으므로 코드 자체는 깔끔하고 깨끗하다.
create event 1003 push event 1000
Java
복사
의사 결정을 최적화하라
도시든 소프트웨어 프로젝트든, 아주 큰 시스템에서는 하 사람이 모든 결정을 내리기 어럽다.
가장 적합한 사람에게 책임을 맡기면 가장 좋다.
관심사를 모듈로 분리한 POJO 시스템은 기민함을 제공한다. 이런 기만함 덕택에 최신 정보에 기반해 최선의 시점에 최적의 결정을 내리기 쉬워진다. 또한 결정의 복잡성도 줄어든다.
결론
시스템 역시 깨끗해야 한다. 깨끗하지 못한 아키텍처는 도메인 논리를 흐르며 기민성을 떨어뜨린다. 도메인 논리가 흐려지면 제품 품질이 떨어진다.
모든 추상화 단계에서 의도는 명확히 표현해야 한다. 그러려면 POJO를 작성하고 관점 혹은 관점과 유사한 메커니즘을 사용해 각 구현 관심사를 분리해야 한다.
시스템을 설계하든 개별 모듈을 설계하든, 실제로 돌아가는 가장 단순한 수단을 사용해야 한다는 사실을 명심하자!