종속성이 없는 진정한 모듈식 코드 만들기
소프트웨어 개발은 훌륭하지만… 처음에는 모든 것이 훌륭합니다. 당신은 일이 아니라면 시간의 문제에 잇달아 새로운 기능을 추가 할 수 있습니다. 당신은 롤에있어!
몇 달 빨리 감기하면 개발 속도가 감소합니다. 예전처럼 열심히 일하고 있지 않기 때문일까? 정말. 몇 달 더 빨리 감으면 개발 속도가 더 떨어집니다. 이 프로젝트에 대한 작업은 더 이상 재미없고 드래그되고있다.
더 심해집니다. 응용 프로그램에서 여러 버그를 발견하기 시작합니다. 종종 하나의 버그를 해결하면 두 개의 새로운 버그가 생성됩니다. 이 시점에서 노래를 시작할 수 있습니다:
99 코드에 작은 버그.99 작은 버그.하나를 내려 놓고 패치,
…코드에서 127 개의 작은 버그.
지금 이 프로젝트에 대해 어떻게 생각하세요? 당신이 저 같이 인 경우에,당신은 아마 당신의 동기부여를 잃기 시작한다. 기존 코드에 대한 모든 변경은 예측할 수없는 결과를 초래할 수 있기 때문에이 응용 프로그램을 개발하는 것은 고통 일뿐입니다.
이 경험은 소프트웨어 세계에서 흔히 볼 수 있으며 많은 프로그래머가 소스 코드를 버리고 모든 것을 다시 작성하려는 이유를 설명 할 수 있습니다.
소프트웨어 개발이 시간이 지남에 따라 느려지는 이유
이 문제의 원인은 무엇입니까?
주요 원인은 복잡성 증가입니다. 내 경험에 비추어 볼 때 전반적인 복잡성에 가장 큰 기여는 대부분의 소프트웨어 프로젝트에서 모든 것이 연결되어 있다는 사실입니다. 각 클래스의 종속성 때문에 전자 메일을 보내는 클래스의 일부 코드를 변경하면 사용자가 갑자기 등록할 수 없습니다. 왜 그럴까요? 등록 코드는 이메일을 보내는 코드에 의존하기 때문에. 이제 버그를 도입하지 않고 아무것도 변경할 수 없습니다. 단순히 모든 종속성을 추적 할 수는 없습니다.
그래서 당신은 그것을 가지고;우리의 문제의 진짜 원인은 우리의 코드가 가지고있는 모든 종속성에서 오는 복잡성을 제기한다.
진흙의 큰 공과 그것을 줄이는 방법
재미있는 것은,이 문제는 지금 년 동안 알려져있다. “진흙의 큰 공이라고 칭한 일반적인 반대로 본입니다.”수년 동안 여러 회사에서 일한 거의 모든 프로젝트에서 이러한 유형의 아키텍처를 보았습니다.
이 안티 패턴은 정확히 무엇입니까? 간단히 말해서,각 요소가 다른 요소와 의존성을 가질 때 큰 진흙 공을 얻습니다. 아래,당신은 잘 알려진 아파치 하둡 오픈 소스 프로젝트에서 종속성의 그래프를 볼 수 있습니다. 진흙의 큰 공(또는 오히려 원사의 큰 공)을 시각화하기 위해 원을 그리고 프로젝트에서 클래스를 균등하게 배치합니다. 그냥 서로에 의존하는 클래스의 각 쌍 사이에 선을 그립니다. 지금 당신은 당신의 문제의 원인을 볼 수 있습니다.
모듈 형 코드와 솔루션
그래서 나 자신에게 질문을:그것은 복잡성을 줄이고 여전히 프로젝트의 시작 부분에서와 같은 재미를 할 수 있을까? 진실을 말하면 모든 복잡성을 제거 할 수는 없습니다. 새로운 기능을 추가하려는 경우 항상 코드 복잡성을 늘려야 합니다. 그럼에도 불구하고 복잡성을 이동하고 분리 할 수 있습니다.
다른 산업이이 문제를 해결하는 방법
기계 산업에 대해 생각해보십시오. 일부 작은 기계 가게 기계를 만들 때,그들은 표준 요소의 집합을 구입 몇 가지 사용자 정의 사람을 만들고,함께 넣어. 그들은 이러한 구성 요소를 완전히 별도로 만들고 끝에 모든 것을 조립하여 몇 가지 비틀기 만 할 수 있습니다. 어떻게 이런 일이 가능할까요? 그들은 볼트 크기와 같은 설정된 업계 표준과 장착 구멍의 크기 및 그 사이의 거리와 같은 사전 결정을 통해 각 요소가 어떻게 서로 맞는지 알고 있습니다.
위의 어셈블리의 각 요소는 최종 제품 또는 다른 조각에 대해 전혀 지식이없는 별도의 회사에 의해 제공 될 수있다. 각 모듈러 요소가 사양에 따라 제조되는 한 계획대로 최종 장치를 만들 수 있습니다.
우리는 소프트웨어 산업에서 그것을 복제 할 수 있습니까?
물론 우리는 할 수 있습니다! 인터페이스 및 제어 원리의 반전을 사용하여;가장 중요한 부분은이 접근 방식은 객체 지향 언어에서 사용할 수 있다는 사실이다:자바,기음#,스위프트,타이프 스크립트,자바 스크립트,
통제의 반전은 당신의 친구입니다
처음 통제의 반전에 대해 들었을 때,나는 즉시 내가 해결책을 발견했다는 것을 깨달았다. 그것은 기존 종속성을 취하고 인터페이스를 사용하여 반전시키는 개념입니다. 인터페이스는 메서드의 간단한 선언입니다. 그들은 구체적인 구현을 제공하지 않습니다. 그 결과,그들은 그들을 연결하는 방법에 대한 두 요소 사이의 계약으로 사용할 수 있습니다. 당신이 경우 그들은 모듈 형 커넥터로 사용할 수 있습니다. 한 요소가 인터페이스를 제공하고 다른 요소가 구현을 제공하는 한,그들은 서로에 대해 아무것도 모른 채 함께 작업 할 수 있습니다. 그것은 훌륭합니다.
간단한 예제를 통해 시스템을 분리하여 모듈 식 코드를 만드는 방법을 살펴 보겠습니다. 아래 다이어그램은 간단한 자바 응용 프로그램으로 구현되었습니다. 이 깃허브 저장소에서 찾을 수 있습니다.
문제
Main
클래스,세 가지 서비스 및 단일Util
클래스로 구성된 매우 간단한 응용 프로그램이 있다고 가정 해 봅시다. 이러한 요소는 여러 가지 방법으로 서로에 따라 달라집니다. 아래에서”큰 진흙 공”접근 방식을 사용하는 구현을 볼 수 있습니다. 클래스는 단순히 서로를 호출합니다. 그들은 단단히 결합되어 있으며,다른 요소를 건드리지 않고 단순히 하나의 요소를 꺼낼 수는 없습니다. 이 스타일을 사용하여 만든 응용 프로그램을 사용하면 처음에는 빠르게 성장할 수 있습니다. 나는 당신이 쉽게 물건을 가지고 놀 수 있기 때문에이 스타일이 개념 증명 프로젝트에 적합하다고 믿습니다. 그럼에도 불구하고 유지 관리조차도 위험 할 수 있고 단일 변경으로 인해 예측할 수없는 버그가 발생할 수 있기 때문에 프로덕션 준비 솔루션에는 적합하지 않습니다. 아래 다이어그램은 진흙 아키텍처의 큰 공을 보여줍니다.
의존성 주입이 모두 잘못된 이유
더 나은 접근 방식을 찾기 위해 의존성 주입이라는 기술을 사용할 수 있습니다. 이 메서드는 인터페이스를 통해 모든 구성 요소를 사용해야 한다고 가정합니다. 나는 그것이 요소를 분리한다는 주장을 읽었지만 실제로는 그렇지 않습니까? 아니 아래 다이어그램을 살펴보십시오.
현재 상황과 큰 진흙 덩어리의 유일한 차이점은 클래스를 직접 호출하는 대신 인터페이스를 통해 호출한다는 사실입니다. 그것은 서로 요소를 분리하는 것을 약간 향상시킵니다. 예를 들어 다른 프로젝트에서Service A
를 다시 사용하려는 경우Interface A
과 함께Interface B
및Interface Util
와 함께Service A
자체를 꺼내어 사용할 수 있습니다. 보시다시피Service A
는 여전히 다른 요소에 따라 다릅니다. 결과적으로,우리는 여전히 한 곳에서 코드를 변경하고 다른 곳에서 동작을 엉망으로 만드는 데 문제가 있습니다. Service B
및Interface B
를 수정하는 경우 해당 요소에 종속된 모든 요소를 변경해야 하는 문제가 여전히 생성됩니다. 이 방법은 아무것도 해결하지 않습니다;내 의견으로는,그것은 단지 요소의 상단에 인터페이스의 레이어를 추가합니다. 당신은 어떤 종속성을 주입해서는 안,대신 당신은 한 번 모두를 위해 그들을 제거해야한다. 독립을 위해 만세!
모듈 식 코드 솔루션
내가 믿는 접근 방식은 종속성을 전혀 사용하지 않음으로써 종속성의 모든 주요 두통을 해결합니다. 구성 요소와 해당 수신기를 만듭니다. 리스너는 간단한 인터페이스입니다. 현재 요소 외부에서 메서드를 호출해야 할 때마다 메서드를 수신기에 추가하고 대신 호출하기만 하면 됩니다. 이 요소는 파일을 사용하고 패키지 내에서 메소드를 호출하며 주 프레임 워크 또는 기타 사용 된 라이브러리에서 제공하는 클래스 만 사용할 수 있습니다. 아래에서는 요소 아키텍처를 사용하도록 수정된 응용 프로그램의 다이어그램을 볼 수 있습니다.
이 아키텍처에서는Main
클래스에만 여러 종속성이 있습니다. 모든 요소를 함께 연결하고 응용 프로그램의 비즈니스 논리를 캡슐화합니다.
반면에 서비스는 완전히 독립적 인 요소입니다. 지금,당신은이 응용 프로그램에서 각 서비스를 꺼내 다른 곳에서 다시 사용할 수 있습니다. 그들은 다른 것에 의존하지 않습니다. 하지만 잠깐,그것은 더 좋아진다:당신은 당신이 그들의 행동을 변경하지 않는 한,다시 그 서비스를 수정할 필요가 없습니다. 만큼 그 서비스는 그들이해야 할 일을 할 때,그들은 시간이 끝날 때까지 그대로 남아있을 수 있습니다. 그들은 전문 소프트웨어 엔지니어,또는 이제까지 혼합goto
문 요리 최악의 스파게티 코드 사람의 손상 처음 코더에 의해 생성 될 수있다. 그들의 논리가 캡슐화되어 있기 때문에 중요하지 않습니다. 그것은 수만큼 끔찍한,그것은 다른 클래스에 밖으로 유출하지 않습니다. 또한 여러 개발자간에 프로젝트에서 작업을 분할 할 수있는 기능을 제공합니다.이 기능을 통해 각 개발자는 다른 개발자를 방해하거나 다른 개발자의 존재에 대해 알지 않고도 자신의 구성 요소를 독립적으로 작업 할 수 있습니다.
마지막으로 마지막 프로젝트의 시작 부분에서와 같이 독립적 인 코드를 한 번 더 작성할 수 있습니다.
요소 패턴
구조적 요소 패턴을 정의하여 반복 가능한 방식으로 만들 수 있도록 하자.
요소의 가장 간단한 버전은 두 가지로 구성됩니다: 주요 요소 클래스와 리스너. 요소를 사용하려면 수신기를 구현하고 기본 클래스를 호출해야 합니다. 다음은 가장 간단한 구성의 다이어그램입니다:
분명히,당신은 결국 요소에 더 많은 복잡성을 추가해야하지만 그렇게 쉽게 할 수 있습니다. 논리 클래스 중 어느 것도 프로젝트의 다른 파일에 의존하지 않도록 하십시오. 이 요소의 기본 프레임 워크,가져온 라이브러리 및 기타 파일 만 사용할 수 있습니다. 이미지,보기,소리 등과 같은 자산 파일의 경우,또한 미래에 쉽게 재사용 할 수 있도록 요소 내에 캡슐화되어야합니다. 당신은 단순히 다른 프로젝트에 전체 폴더를 복사 할 수 있습니다 거기에있다!
아래에서 고급 요소를 보여주는 예제 그래프를 볼 수 있습니다. 사용 중인 뷰로 구성되며 다른 응용 프로그램 파일에 의존하지 않습니다. 종속성을 확인하는 간단한 방법을 알고 싶다면 가져오기 섹션을 참조하세요. 현재 요소 외부의 파일이 있습니까? 그렇다면 이러한 종속성을 요소로 이동하거나 수신기에 적절한 호출을 추가하여 제거해야 합니다.
또한 자바에서 만든 간단한”안녕하세요 세계”예를 살펴 보자.
public class Main { interface ElementListener { void printOutput(String message); } static class Element { private ElementListener listener; public Element(ElementListener listener) { this.listener = listener; } public void sayHello() { String message = "Hello World of Elements!"; this.listener.printOutput(message); } } static class App { public App() { } public void start() { // Build listener ElementListener elementListener = message -> System.out.println(message); // Assemble element Element element = new Element(elementListener); element.sayHello(); } } public static void main(String args) { App app = new App(); app.start(); }}
처음에는ElementListener
을 정의하여 출력을 인쇄하는 방법을 지정합니다. 요소 자체는 아래에 정의되어 있습니다. 요소에sayHello
를 호출하면ElementListener
을 사용하여 메시지를 인쇄합니다. 요소가printOutput
방법의 구현에서 완전히 독립적이라는 것을 알 수 있습니다. 콘솔,실제 프린터 또는 멋진 사용자 인터페이스로 인쇄 할 수 있습니다. 요소는 해당 구현에 의존하지 않습니다. 이 추상화 때문에이 요소를 다른 응용 프로그램에서 쉽게 재사용 할 수 있습니다.
이제 메인App
클래스를 살펴보십시오. 리스너를 구현하고 구체적인 구현과 함께 요소를 어셈블합니다. 이제 우리는 그것을 사용하기 시작할 수 있습니다.
요소 아키텍처
대규모 응용 프로그램에서 요소 패턴을 사용하는 방법을 살펴보겠습니다. 그것은 작은 프로젝트에 보여 한 가지-그것은 현실 세계에 적용하는 또 다른입니다.
사용하려는 전체 스택 웹 응용 프로그램의 구조는 다음과 같습니다:
src├── client│ ├── app│ └── elements│ └── server ├── app └── elements
소스 코드 폴더에서 우리는 처음에 클라이언트 및 서버 파일을 분할합니다. 브라우저와 백 엔드 서버:그들은 두 개의 서로 다른 환경에서 실행하기 때문에 그것은 할 수있는 합리적인 일이다.
그런 다음 각 레이어의 코드를 앱 및 요소라는 폴더로 분할합니다. 요소는 독립적 인 구성 요소가있는 폴더로 구성되며 앱 폴더는 모든 요소를 함께 연결하고 모든 비즈니스 논리를 저장합니다.
이렇게 하면 여러 프로젝트 간에 요소를 재사용할 수 있으며,모든 응용 프로그램별 복잡성은 단일 폴더에 캡슐화되어 요소에 대한 간단한 호출로 축소되는 경우가 많습니다.
실습 예
연습 항상 트럼프 이론을 믿고,의 노드에서 만든 실제 예제를 살펴 보자.그 이유는 무엇입니까?
실생활 예
고급 솔루션의 출발점으로 사용할 수있는 매우 간단한 웹 응용 프로그램입니다. 그것은 요소 아키텍처를 따를뿐만 아니라 광범위하게 구조 요소 패턴을 사용합니다.
하이라이트에서,당신은 메인 페이지가 요소로 구별 된 것을 볼 수 있습니다. 이 페이지에는 자체 보기가 포함되어 있습니다. 예를 들어,당신이 그것을 재사용 할 때,당신은 단순히 전체 폴더를 복사하고 다른 프로젝트에 드롭 할 수 있습니다. 그냥 와이어 모든 것을 함께 당신은 설정됩니다.
오늘 자신의 응용 프로그램에서 요소를 도입 할 수 있음을 보여주는 기본 예입니다. 독립적 인 구성 요소를 구별하고 논리를 분리 할 수 있습니다. 현재 작업중인 코드가 얼마나 지저분한지는 중요하지 않습니다.
더 빨리 개발하고 더 자주 재사용하십시오!
이 새로운 도구 세트를 사용하면 유지 보수가 더 쉬운 코드를 더 쉽게 개발할 수 있기를 바랍니다. 당신이 실제로 요소 패턴을 사용하여 이동하기 전에,의 신속하게 모든 주요 포인트를 정리해 보자:
-
소프트웨어의 많은 문제는 여러 구성 요소 간의 종속성 때문에 발생합니다.
-
한 곳에서 변경함으로써 다른 곳에서 예측할 수없는 행동을 도입 할 수 있습니다.
세 가지 일반적인 아키텍처 접근 방식은 다음과 같습니다:
-
진흙의 큰 공입니다. 그것은 급속한 발달을 위해 중대합니다,그러나 안정되어 있는 생산 목적을 위해 이렇게 중대합니다.
-
의존성 주입. 그것은 당신이 피해야 할 반 구운 해결책입니다.
-
요소 아키텍처. 이 솔루션을 사용하면 독립적 인 구성 요소를 만들고 다른 프로젝트에서 다시 사용할 수 있습니다. 안정적인 생산 릴리스를 위해 유지 보수가 가능하고 훌륭합니다.
기본 요소 패턴은 모든 주요 메서드가 있는 기본 클래스와 외부 세계와 통신할 수 있는 간단한 인터페이스인 리스너로 구성됩니다.
풀 스택 요소 아키텍처를 구현하려면 먼저 프런트 엔드를 백엔드 코드에서 분리합니다. 그런 다음 응용 프로그램 및 요소에 대한 각각의 폴더를 만듭니다. 요소 폴더는 모든 독립적 인 요소로 구성되며 앱 폴더는 모든 것을 함께 연결합니다.
이제 자신의 요소를 만들고 공유 할 수 있습니다. 장기적으로,그것은 당신이 쉽게 유지 보수 할 수있는 제품을 만드는 데 도움이됩니다. 행운을 빌어 요 그리고 당신이 만든 것을 알려주십시오!
또한 코드를 조기에 최적화하는 것을 발견하면 동료 탑 탈러 케빈 블로흐의 조기 최적화의 저주를 피하는 방법을 읽으십시오.