본문 바로가기

issue & tip

백세코딩#소프트웨어 개발의 기본

반응형

소프트웨어 개발의 기본

'객체지향'의 원리는 현대의 소프트웨어 개발의 핵심인 유지보수하기 쉽고 유연하게 확장되는 소프트웨어를 만드는데 가장 충실한 기초 역할을 한다.


OOP의 기본원칙은

'하나의 클래스나 모듈은 하나의 기능을 해야 한다.'

'가능한 확장성을 고려해서 만들어져야 한다'

'클래스와 모듈 접근하는 인터페이스는 단일해야 한다.'

'인터페이스는 하나의 기능과 모듈을 불러야 한다'

'모듈이나 클래스는 메시지를 통해서 연결되어야지 직접호출하거나 직접 연계되어서는 안된다.'


이러한 원리는 SOLID라는 기본적인 원칙

소프트웨어 개발자들은 경험이 풍부해지기 전에 '내가 만든 코드가 재사용되기보다는 선배나 다른 사람이 만들어 놓은 코드를 흉내내기' 때문에 SOLID의 원칙에 대해서는 충분한 연습을 한 다음에 다시 생각해보자.

당장 코딩을 하면서 이 원칙을 모두 적용해서 프로그래밍 하는 것을 권고하지 않는다.

어설프게 이 원칙을 사용하게 되면 오히려 복잡한 코드가 만들어질 수 있다.


'코딩은 쉽고 이해하기 쉽도록 노력하라'

'하나의 클래스나 모듈에 하나의 인터페이스를 사용하도록 노력하라'

이 두가지 기본적으로 튼튼하게 하기를 권한다.

그리고 충분하게 경험적으로 '필요성'을 느끼게 된 이후에 5대 원칙을 제시한다.


OOP의 5대원칙


1. SRP(Single Responsibility Principle) 단일책임원칙

한 객체는 하나의 책임을 져야 한다는 원칙으로 높은 응집도와 낮은 결합도를 기본으로 한다는 것을 의미한다.

다만, 그 책임의 범위는 정해진 원칙이나 기준에 따라서 조금씩 다르다.

사실, 이렇게 설명하는 말이 더 어렵다.

그냥, '하나의 클래스에 하나의 기능', '하나의 모듈에 하나의 기능' 이렇게 설명하기기 쉽다.

하나의 책임이라는 부분이 규모와 기능적인 중요도에 따라서 조금씩 달라지고 이 원칙은 각각의 아키텍처 마다 그 기준을 다르게 하게 된다.

이 관점은 유지보수의 관점에 따라 다르다.

어떤 관점으로 유지보수를 하느냐에 중요하다.

해당 '책임'을 가지는 객체에 대한 수정은 하나의 공간에서 이루어지면 된다.

보통 기업이나 개발의 형태, 환경에 따른 가이드라인이 그러한 기준이 된다.

보통은 아키텍처의 크기와 품질의 기준에 따라서 '범위'와 '크기'가 적절하게 결정되는데 그 때에 이 SRP원칙에 따라서 적절한 규모로 제어된다.

자신이 속한 팀이나 프로젝트에서 정규화된 규칙에 습관적으로 반응해야 하고 현업을 하기 위한 기초 단위가 되면 당연하지만, 이렇게 구현되면 '클래스의 이름과 개념, 속서 등이 매우 명쾌해진다'

그리고 적절하게 객체 간의 응집력이나 병합과 관련된 판단의 근거들을 고민하게 된다.


2. OCP(Open Closed Principle) 개방-폐쇄원칙

확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙으로 기존의 클래스를 수정하지 말고 상속이나 구현으로 확장을 해야 한다는 것이다.

이 개념은 조금 복잡할 수 있다.

하지만, 이원칙을 사요아게 되면 가장 기본적으로 지켜야할 규칙은 '한번 만들어진 인터페이스 바꾸지마'라고 설명하면 되겠다.

보통 이 개념은 '유지보수 비용' 때문에 앞으로 중요한 요소로 작용하게 된다.

이 OCP가 원칙적으로 가동되어야만 '재사용'이 높아진다.

OCP의 중요 개념은 추상화와 다형성이고 객체지향을 운용하게 되는 매우 중요한 개념이다.

어렵게 설명했지만, 한번 만들어진 상위 클래스의 내부가 변경되면 인터페이스가 변할 수 있고, 인터페이스가 변하면 해당 상위 클래스의 재사용성이 떨어지게 된다. 상위클래스는 한번 만들어지면 정말 바꾸기 어렵다.


Open: is-A 관계로 파생될수 있어야 한다는 뜻

close: has - A 관계로 영향을 받지 않는다는 뜻


상위 클래스는 하위 클래스의 공통적인 부분들을 모두 가지고 있기 때문에 하위 클래스는 상위 클래스의 기본 기능을 바꾸거나 간섭하며 안된다는 것이다.

선임이나 팀장이 만든 클래스를 상속받아 만든 개발자가 상위 클래스의 기본기능이나 간섭을 하는 코딩을 하지 않도록 상위 클래스의 한계와 범위가 명확하고 그 클래스의 기능과 역할에 대해서 명쾌하게 전달되어야 한다.

보통, OCP 콘셉트가 무너는 경우를 보면, 선임 개발자들이 상위 클래스를 만들면서 필요한 지식과 경험을 제대로 전달하지 못할 때에 이 원칙이 깨지게 된다.

그래서 상위 클래스를 디자인하는 사람들은 필요한 지식을 충분하게 정의하고 명쾌하게 전달해야 한다.


3. LSP(The Liskov Substitution Principle) 리스코프 교체 원칙

상퀴를래스는 파생클래스를 대체 가능해야 하는 원칙으로 기반 클래스의 기능은 파생클래스가 포함해야 한다는 내용이다.

따라서 파생 클래스는 상위클래스보다 더 많은 기능을 제공하게 된다.

이 개념은 보통은 후배 개발자들에게 전달하기 어려운 개념이기도 하다.

이 LSP 원칙은 중요 상위 클래스를 변경하거나 프레임워크로 사용하던 내용을 업그레이드 하면서 이 원칙을 무너트린 프레임워크에 고생을 해봐야 이 원칙이 왜 중요한지 알 수 있다.

다만, 이러한 원칙을 잘 지킨 프레임워크를 만나기도 참으로 어렵다는 것을 나중에 알게 될 것이다.

물론, JAVA 언어로 넘어오면서 이 원칙은 생각보다 잘 지켜진다.

그래서 JAVA가 참 유지보수하기 쉽고 프레임워크 대체도 매우 수월한 체계라는 것을 알 수 있다.

이 개념은 자식 클래스는 부모클래스의 자리로 대체될 수 있어야 한다는 뜻으로 설계 시에 사용하기보다는 설계된 클래스를 활용하는 단계에서 요구된다.

그리고 이 LSP원칙은 함수에 매우 의존적이다.

말그대로, 상위 클래스를 파생 클래스로 만든 자식은 당연하게도 상위 클래스를 대체할 수 있어야 한다.

그래서 상속관계가 이어진다.

이 부분 또한, 앞으로 유지보수를 위한 원칙과 규칙에 해당한다.

보통은 상위 클래스를 만들어서 사용하였지만, 속도 개선이나 특정한 비기능적인 요소들의 개선을 위해서 관련된 코드를 수정할 때 이 원칙을 지키고 있다면 매우 수월하게 변경할 수 있다.


4. DIP(Dependency Inversion Principle) 의존 관계 역전 원칙

클라이언트는 상세 클래스가 아닌 추상화(인터페이스, 추상 클래스)레이어에 의존해야한다는 원칙으로 확장 이슈가 있는 부분은 부분 추상화를 해야 한다는 내용이고, 추상 클래스는 파생클래스를 참조해서는 안되고 파생클래스나 추상 클래스는 오직 추상 클래스만 참조해야 한다.

즉, 클래스 간의 의존관계는 추상 클래스나 인터페이스에 의한 의존이어야 한다는 뜻이다.

더 쉽게 모듈이나 클래스 간의 의미관계는 최대한 '메시지'를 주고 받는 관계로써 유지보수 시에 가능한 영향을 받지 않도록 하라는 뜻이다.

파생된 녀석을 추상화한 녀석이 바라본다는 발상 자체가 틀려먹었다는 것이다.

고차원적인 모듈이 저차원적인 모듈에 의존하면 안된다는 것이다.

물론, 상위클래스가 되는 녀석은 자주 변경되는 구상 클래스(Concreate Class)가 되면 안된다.

하지만, 요구사항이나 구조, 아키텍처가 변하면 당연하게 변화가 일어난다.

초기 분석이 잘못되면 모두 꼬이는 것이 매우 당연하다.

말이 조금 어렵지만, 쉽게 이야기한다면 대부분의 통신 프로그램이 모델이 이러한 DIP원칙을 지키고 있다.

현재 통신 프로그래밍은 대부분 이벤트 드리븐이나 콜백, JMS의 모델과 같은 event model을 대부분 사용한다

이 원칙이 DIP의 원리이다.


5.ISP(Interface Segregation Principle) 인터페이스 격리 원칙

클라이언트에 특화된 여러 개의 인터페이스가 하나의 범용 인터페이스보다 좋다는 것으로 인터페이스는 범용적으로 사용되는 것이 아니라는 이야기다.

인터페이스는 정말 최소화로 만들어야 한다는 것이다.

SRP는 클래스의 단일화된 기능과 책임을 ISP는 인터페이스의 단일화된 기능과 책임을 의미한다.

최소한의 인터페이스만 구현해야, 해당 객체는 재사용성이 극대화된다.

클라이언트에 특화된 인터페이스가 별도로 존재하는 것이 타당하다.

해당 정보만 교환되면 된다.

중요한 것은 이러한 개념들을 매우 자연스럽게 읷구해진다는 것 자체가 매우 어렵다.

평생 프로그래밍을 한다고 하더라도 정수 선언하는 것은 그나마 제대로 할 것이지만, 개발의 기본이 되는 경향(Tendency)과 흐름(Stream)이다. 그리고 더 중요한 것은 '남이 읽을 수 있는 코드'를 만들자는 더 기본적인 원칙을 지키자.


결론적으로 OOP는 유지보수가 쉬운 소스의 개발이 원칙이다.

수정이나 변화에 적극적인 소스코딩을 의미한다.

이런 원칙을 지킨다고 하더라도 쉽게 만들어야 하는 원칙을 어기면 그것은 OCP가 아니다.

복잡한 클래스의 구조를 만드는 것 자체가 OOP를 어기는 행위다.

아무리 SOLID의 기본 원칙을 잘 지키고 있다고 하더라도 '쉽지 않다면 잘못된 코딩'이다.


좋은 설계

Quality of Design 이해하기 쉽고 바꾸기 쉬워야 하며 어렵지 않고 경제적이어야 한다.


나쁜 설계

Bad Design


1. 경직성이 높아서 무엇이든 하나를 바꿀 때마다 다른 것도 바꿔야 한다는 것.

그래서 또 다른 것을 바꿔야 하는 것을 의미한다.

SRP원칙을 어긴것이다.

2. 시스템의 한 부분을 수정했는데 그것과 전혀 상관없는 부분에 영향을 주는 것

3. 부동성의 원칙으로 시스템을 여러 컴포넌트나 서비스로 분해할 수 없는 구조를 의미

4. 수정하고 컴파일하고 테스트하는 과정이 길고 복잡한 것

5. 과도한 디자인으로 복잡하게 만든 코드를 의미.

지금 당장은 필요없지만, 언젠가 굉장히 유용할 것으로 생각하고 만든 부분둘이다.

6.필요없는 반복으로 비슷한 코드가 반복되는것

7.불투명함으로써 코드를 만든 의도를 잘 모르겠고 설명도 안되는 부분이며 심시지 설명자료도 부족한 경우를 의미


프레임워크와 컨테이너들에 대한 개념들

대부분 IoC(Inversion of Control 제어의 역행)를 구현하고 있다고 설명할 수 있다.

현대의 프레임워크들의 매우 일반적인 특성이기도 하다.

대부분의 프레임워크들은 main loop 구조를 가지고 있고 그것에 사응하는 UI 프레임워크라면 스크린의 다양한 '필드'를 위한 이벤트 핸들러를 제공하고 있을 것이다.

개발자들은 이 이벤트 핸들러에 필요한 코드를 담게 된다.

프로그램의 제어는 UI 프레임워크가 가진 것이며 프로그래머는 동작할 수 있는 형태들을 채우는 것 뿐이다.

마틴파울러는 이러한 IoC들을 의존성 주입이라고 이름을 붙였다.

이 방식은 전통적인 개발 방식에서 '제어의 권한'을 소프트웨어 개발자들이 쥐고 있엇는데 IoC에서는 전적으로 프레임워크가 그러한 권한을 가지고 있는 것을 의미한다.

복잡하게 이야기 하면 IoC는 프레임워크가 정의한 추상화(인터페이스 또는 추상클래스)을 클라이언트 코드로 구현하고 구현된 객체를 프레임워크에 전달(또는 주입), 프레임워크가 제어를 하게 함으로써 클라이언트 코드로부터 제어의 수를 줄이게 하는 것을 의미한다.

모든 제어를 클라이언트 코드가 가진 구조적인 설계와 비교해서 프레임워크가 제어를 가지는 것을 제어가 역전되었다고 말한다.


'기본적으로 구동되는 프레임워크가 이벤트 핸들러를 가지고 있고 개발자는 이벤트 핸들러가 반응하는 코드만 넣어주면 소프트웨어는 완성되는 형태이다'

이 방식의 장점은 소프트웨어 개발자가 필요한 코드만 작성하므로 프레임워크의 기본적인 제어에 영향을 주지 않으므로 매우 안전한 동작을 보장하는 방식이다.

현재의 Java 기반의 대부분의 프레임워크는 이러한 IoC의 형태로 구성되어 있다.

그리고 필요한 부분들의 구현 클래스들은 프러그인(Plugin)의 형태로 기술하여 동작하게 할 수 있다.

실제 시스템은 수십개의 이런 서비스와 컴포넌트들로 구성된다. 그리고 플러그인들을 자유롭게 조립하면 된다.

이것이 경량 컨테이너이다.

IoC를 이용해서 해결하는 방법이다.

탑재방법

Constructor Injection

Setter Injection

Interface Injection

그리고 주입할 객체들을 생성하는 설정들을 코드에 넣어두거나 설정파일(XML)을 사용하는 것이 일반적이다.

정해진 프레임워크의 추상화된 이벤트에 코드를 넣고 XML 파일만 설정하면 필요한 프러그인들이 삽입되어 동작한다.

그 규칙들만 잘 지키면 된다는 뜻이다.

현재 주로 사용하는 Spring, EJB3 등은 이러한 Dependency Injectioin DI라고 불리는 개념으로 구성되어있다.

(Struts2, Hibernate, Grails 등)

더 드라이하게 설명하면, 정해진 규칙으로 칸 메우기만 잘하면 나머지는 프레임워크가 알아서 동작시킨다.

그리고 해당 프레임워크는 정해진 권한을 가진 워킹그룹이거나 기업, 공급업체가 책임진다.


프로그래밍은 구조가 더 중요하다.

대부분 IoC 기반으로 DI 상태의 프레임워크들로 프로그래밍이 이루어지다 보니 가장 중요한 것은 '구조'다.

알고리즘마저도 경량화된 컨테이너로 적절하게 구성해서 탑재하는 구조이다 보니 프로그래밍 자체가 매우 쉬운진 것 또한 사실이다.

거기에 클라우드 방식들은 네트워크 기술을 모르더라도 완전 추상화된 상태에서 서비스 구조들을 자연스럽게 확장할 수 잇는 구로를 AWS가 제공하고 있다.

정말 프로그래밍하기 쉬운 시대다.

거기에 디바이스들은 Python과 같이 경량화된 스크립트들로 가동되고 있으니 정말 프로그래밍은 구조의 싸움이 되어버렸다.

설계를 하지 않아도 좋은 코드의 작성이 가능한 사람이 있다.

하지만, 그런 선천적인 프로그래머들이 아니라면 설계를 하는 시간을 가능한 최대로 해야한다.


현재와 미래를 관통하는 코드 개발

현재 각자의 개발환경에서 무엇을 체크해야 하는지 알아봐야한다.

가장 먼저 '방법론'이라는 불리는 프로세스가 조직에 내재화되어 있는지 체크하자.

나름의 규칙과 성숙하게 개발조직의 문화를 만들지 못하고 있다면 이 부분을 어떻게 개선할 것인가를 가장 우선시 여겨야 한다.

문화를 받아들일 준비가 되어 있지 않다면 그 어떤 도구도, 그 어떤 최고의 기술도 무용지물이다.

그 다음에 체크 할 것은 '설계'와 '패턴'에 대해서 개발자들이 '인지'하고 있느냐를 체크해야한다.

각자 '설계' 단계에 대해서 구분을 하기 시작했고, '의식화'되어 있는지를 알아야 한다.

그리고 '패턴'이라는 것은 현재 조직 내부에서 구사하는 소프트웨어 코드들이 '패턴'이라는 불리는 것으로 내부적에서 사용되고 있느냐 하는 것이다.

누가 만들었던 코드던 서비스던 중복적인 투자가 발생하지 않도록 내부적으로 '패턴'을 효율적으로 사용하고 있는지, 개발자들이 서로 소통하고 있는지를 알아야 한다.

이러한 '패턴'을 인지하는 단계는 조직 내부의 '라이브러리'라고 불리는 디지털 지식이 충분하게 협업 되는 상태로 모여지고 사용되고 있다는것을 의미한다.

아직 패턴화된 상태로 흘러다니고 있지 못하다면 먼저 가장 효율성이 높은 라이브러리르 최대한 축적하고 이를 조직 내부에서 적극적으로 활용할 수 있도록 가이드해야 한다.

이렇게 '방법론'이 존재하고 '설계'와 '패턴'을 인지하는 조직이라면 당연하게도 통합적인 방법이나 데일리빌드, CI 체계를 갖추는 훌륭한 조직이 될 수 밖에 없다.

개발자들은 자연스럽게 Unit Test를 그 체제하에서 가동하고 있고 Test Case의 Coverage에 대해서 논의한다.

그리고 자연스럽게 태스크(Task)를 어떻게 자동화하는 것이 반복적인 노가다를 줄여줄 것인가에 대해서 이야기하고 이를 주변에 널리 공용적으로 사용하려 한다.

자연스럽게 '품질'이라는 단어를 사용하지 않아도 이미 이 조직은 '품질 좋은 코드'를 만들 준비가 돼 있는 조직이다.

이 단계를 넘어선 조직은 자연스럽게 '문서화 도구'들을 사용한다.

JavaDoc나 JSDOC3와 같은 도구들을 사용하며 소스의 주석을 마킹하는 떄에 그 의미와 형태에 대해서 일관성이 있는 방법을 사용하여 코딩하기 시작한다.

사실 개발 조직 내부에 들어오는 개발자들에게 이 '코딩 가이드라인'을 초기서부터 익숙하게 해주는 것이 가장 좋다는 것을 인지한 개발조직의 성숙도는 매우 높은 단계라고 볼 수 있다.

초보개발자들은 이런 환경에서 일할 기회를 만난다면 필사적으로 해당 조직에서 버틸 수 있도록 애를 쓰는 것이 좋을 것이다.

현재의 개발도구들은 매우 고도화되어서 전달인자의 타입에 힌트를 주거나 타입의 오류에 대해서 문제가 있다고 판단되는 것에 친철하게 가이드와 경고 표시를 해준다.


Refactoring의 기본 5가지

코드를 새롭게 작성하는 것이 아니라 코드 수정으로 발생하는 오류를 최대한 정제하는 기법이지만, 언제나 코드를 작성하는 기준이기도 하다.

쉽게 이야기해서 1차 코딩을 하다보면 초기에 생각하지 않았던 의미들이 추가되면서 기존에 부여된 '이름 규칙'에 혼선이 발생하고 이 혼동된 규칙들이 정리정돈하는 것이 리팩토링의 가장 기본적인 원리다.

그래서 가장 원천적인 리팩토링의 기본은 '이름'을 깔끔하게 정리하는 것이다.


1. 메서드의 이름의 의미를 제대로 전달하고 있지 못하다면 의미 있는 이름으로 바꾸어라.

2. 변수나 객체의 의존관계를 파악하여 위험한 관계를 해결하는 것


java의 경우에 public 메서드를 삭제하거나 수정할 때 다른 곳에서 사용하고 있는지를 판단해야 한다.

그리고 의존관계에 대해서 명쾌하게 접근해야 한다.


3. inline method로 너무 작고 간결한 코드는 굳이 메서드를 만들지 말고 직접 사용하는 것이 좋다.

하지만 조직 내부에 코드 리뷰가 가능한 사람이 없다면 가능한 앞으로 리팩토링을 위해서 메서드를 더욱 더 사용하라고 권하고 싶다.


4. Extract Parameter로 함수나 메서드에 사용되는 '값'들이 존재한다면 모두 추상화된 값으로 전달하는 것이 좋다.

가능한 '숫자'로 존재하고 '유의미한 형태'로 존재한다면 '이름'을 가지는 것이 가장 효과적이고 해당 숫자로 범위를 판단하거나 그 형태의 존재의 오류를 파악하기 위해서 '이름'을 부여하는 것이 좋다.


5. Extract Method로 그룹으로 한꺼번에 다룰 수 있는 코드 조각은 그 의미가 명확하도록 비슷한 이름을 지어서 별도의 관리가 필요하다는 것이다.

언제나 '의미 있는 단어의 사용'을 중요시 한다.

그리고 '실수'를 줄이려면 이러한 '의미 있는 이름을 붙이는 것'을 언제나 강조한다.

이름만 제대로 부여하면 대부분의 '실수'를 줄일 수 있다.

언제나 '코드'후에 '검토'하고'리뷰'를 통해서 추가된 개념이나 의미가 코드 상에 반영되도록 하면서 최신의 개념을 유지하게 하는 것이 리팩토링의 가장 우선적인 개념이다.

개발자들은 꼭 리팩토링의 기본단계는 최소한 진행하기 바란다.


소프웨어 개발자가 알아야 하는 몇가지 이야기

-소프트웨어 아키텍트가 알아야 할 97가지

-소프트웨어 누가 이렇게 개떡같이 만드는거야?

-위대한 게임의 탄생


제대로된 소프트웨어 개발자가 되려면?

1. 소프트웨어 이론이 정립된 사람

-자료구조라고 이야기 하는 소프트웨어와 관련된 기초이롬이 튼튼해야 하는 것이다.

아무리 빠르게 변하는 소프트웨어의 세계지만, 기본적인 기초이론은 아마도 변하지 않을 것이다.

2. 오픈소스와 같은 개발 커뮤니티에서 공동으로 꿈을 꾸는 무언가를 만들어 보는 것이다.

- 적극적으로 무언가를 위해서 자신의 시간을 투자하고 '잉여'를 제대로 활용할 줄 아는 사람으로 변화하게 한다.

3. 폭넓은 상식과 인문학적인 개념이다.

- 이제 소프트웨어는 사람과 사람을 이해하지 못하면 서비스를 제대로 구현하지 못하는 시대가 되었다.

이제는 세상의 새로운 가치를 창출하거나 사람들이 즐거워할 그 무언가를 위해서 자신의 '잉여'를 풀 줄 아는 사람이 최고의 인재로 대우 받는 세상이다.


출처-백세코딩

반응형

'issue & tip' 카테고리의 다른 글

스레드  (0) 2018.04.24
문자열과 날짜  (0) 2018.04.24
백세코딩#코드리뷰  (0) 2018.04.16
백세코딩 #빅데이터  (0) 2018.04.16
백세코딩 #소프트웨어 개발 방법론과 DevOps  (0) 2018.04.16