Day 4 mission 2 [SOLID에 대하여 자기만의 언어로 정리해 봅시다.]
우리는 왜 SOLID한 코드를 작성해야 하는지 논의 해볼 필요가 있다.
SOLID의 근원을 찾아보면 애자일에서 찾아볼수 있다. 애자일 선언을 보면 다음과 같다
공정과 도구보다 개인과 상호작용을
포괄적인 문서보다 작동하는 소프트웨어를
계약 협상보다 고객과의 협력을
계획을 따르기보다 변화에 대응하기를
상호작용과 변환에 대응 하며 작동하는 소프트웨어가 우리는 필요하다. 점차 고도화되는 레거시에 대응하기 위한 방법이 SOLID 원칙을 지킨 코드라고 할 것이다.
로버트 마틴의 Agile Software Development, Principles, Patterns, and Practices: Pearson New International Edition 을 보면 유지보수하기 힘든 코드는 아래와 같은 취약점을 가진다고 한다.
경직성(Rigidity) : 설계를 변경하기 어려움
취약성(Fragility) : 설계가 망가지기 쉬움
부동성(Immobility) : 설계를 재사용하기 어려움
점착성(Viscosity) : 제대로 동작하기 어려움
불필요한 복잡성(Needless Complexity) : 과도한 설계
불필요한 반복(Needless Repetition) : 마우스 남용
불투명성(Opacity) : 혼란스러운 표현
SOLID는 경직성, 취약성, 부동성, 점착성을 보완하기 위한 방법이라 할 수 있다. 중요한것은 해당 원칙은 언제나 보완책이며 절대적인 이론이 아니다.
그러면 SOLID 원칙은 어떠한 것이 있는지 확인하고 어떤 방식으로 해당 취약점을 보완하는지 살펴보자.
SOLID원칙은 아래와 같다.
Single Responsibility Principle (SRP, 단일 책임 원칙) - 경직성(Rigidity), 점착성(Viscosity) 보완
Open/Closed Principle (OCP, 개방-폐쇄 원칙) - 경직성(Rigidity), 부동성(Immobility) 보완
Liskov Substitution Principle (LSP, 리스코프 치환 원칙) - 취약성(Fragility) 보완
Interface Segregation Principle (ISP, 인터페이스 분리 원칙) - 취약성(Fragility) , 부동성(Immobility) 보완
Dependency Inversion Principle (DIP, 의존 역전 원칙) - 경직성(Rigidity), 부동성(Immobility) 보완
어떻게 줄이는지 아래의 코드를 보자.
Single Responsibility Principle (SRP, 단일 책임 원칙)
책임을 나누어 코드의 경직성을 줄인다.
public static void main(String[] args) {
사람 사람 = new 사람();
사람.커피사서회사간다();
}
public class 사람 {
public void 커피사서회사간다() {
System.out.println(" 커피를 산다.");
System.out.println(" 회사에 간다.");
}
}
위 코드에서 커피말고 녹차를 마신다면, 저 코드는 필연적으로 수정을 수반한다.
반면 메소드를 구분하면 동일한 부분은 수정을 하지 않을 수 있다.
public static void main(String[] args) {
사람 사람 = new 사람();
//사람.커피산다();
사람.녹차산다();
사람.회사간다();
}
public class 사람 {
public void 커피산다() {
System.out.println(" 커피를 산다.");
}
public void 녹차산다() {
System.out.println(" 녹차를 산다.");
}
public void 회사간다() {
System.out.println(" 회사에 간다.");
}
}
Open/Closed Principle (OCP, 개방-폐쇄 원칙)
다형성을 이용하여 클라이언트 코드를 변경하지 많고 확장하여 부동성(Immobility)을 줄인다.
public static void main(String[] args) {
한국인 _한국인 = new 한국인();
if(_한국인 instanceof 한국인){
_한국인.녹차산다();
}
if(_한국인 instanceof 미국인){
_한국인.커피산다();
}
_한국인.회사간다();
}
}
public class 한국인{
public void 커피산다() {
System.out.println(" 커피를 산다.");
}
public void 회사간다() {
System.out.println(" 회사에 간다.");
}
}
public class 미국인{
public void 커피산다() {
System.out.println(" 커피를 산다.");
}
public void 회사간다() {
System.out.println(" 회사에 간다.");
}
}
위 코드에서 중국인이나 일본인이 추가되면 우리는 if문을 계속 추가해줘야한다.
하지만 다형성을 이용하면 클라이언트의 코드의 변경을 따로 확인하지 않아도 처리 할 수 있다.
public static void main(String[] args) {
회사 _한국인 = new 한국인();
_한국인.회사가기();
}
public class 한국인 implements 회사{
public void 녹차산다() {
System.out.println(" 녹차를 산다.");
}
public void 회사간다() {
System.out.println(" 회사에 간다.");
}
@Override
public void 회사가기() {
녹차산다();
회사간다();
}
}
public class 미국인 implements 회사{
public void 커피산다() {
System.out.println(" 커피를 산다.");
}
public void 회사간다() {
System.out.println(" 회사에 간다.");
}
@Override
public void 회사가기() {
커피산다();
회사간다();
}
}
public interface 회사 {
void 회사가기();
}
Liskov Substitution Principle (LSP, 리스코프 치환 원칙)
자식이 부모의 기능도 구현가능하게 하여 상속 구조의 안전성을 보장해 취약성(Fragility)을 줄인다
public static void main(String[] args) {
사람 _사람 = new 눈사람();
_사람.말하기();
}
abstract class 사람 {
abstract void 말하기();
}
public class 눈사람 extends 사람{
@Override
void 말하기() {
System.out.println("나는 말을 못해요");
}
}
눈사람은 말을 할 수 없다. 하지만 사람이기 때문에 해당 기능을 구현해야 했고 예상하지 못한 오류나 마찬가지다.
public static void main(String[] args) {
인간 _사람 = new 일본인();
_사람.말하기();
}
public class 일본인 extends 사람 implements 인간{
@Override
public void 말하기() {
System.out.println("나는 말 해요");
}
}
public interface 인간 {
void 말하기();
}
사람이면서 인간이기 때문에 일본인은 말할수 있다.
Interface Segregation Principle (ISP, 인터페이스 분리 원칙)
ISP는 많은 기능을 가진 인터페이스를 개별 인터페이스로 분리하여 부동성(Immobility)을 줄인다.
public static void main(String[] args) {
강아지 _강아지 = new 강아지();
_강아지.말하기();
}
public class 강아지 implements 포유류{
@Override
public void 달리기() {
System.out.println("가능");
}
@Override
public void 말하기() {
System.out.println("불가능");
}
}
public interface 포유류 {
void 달리기();
void 말하기();
}
강아지가 말을 하면 좋겠지만 할 수 없다. 이건 인터페이스가 너무 많은 기능을 가져서 그렇다. 분리하자
public static void main(String[] args) {
강아지 _강아지 = new 강아지();
_강아지.달리기();
}
public class 강아지 implements 포유류{
@Override
public void 달리기() {
System.out.println("가능");
}
}
public interface 포유류 {
void 달리기();
}
public interface 인간 {
void 말하기();
}
5. Dependency Inversion Principle (DIP, 의존 역전 원칙)
DIP와 결합도를 낮춰 부동성(Immobility)을 줄인다.
public static void main(String[] args) {
자동차 자동차 = new 자동차();
한국인 _한국인 = new 한국인();
자동차.운전하기(_한국인);
}
public class 자동차 {
public void 운전하기(한국인 _한국인){
_한국인.운전하기();
}
}
public class 한국인 {
public void 운전하기() {
System.out.println("부웅");
}
}
자동차는 한국인이 다른 사람으로 바뀌면 운전을 할 수 없다. 이건 자동차가 한국인에 의존하고 있기 때문이다. 그렇다면 한국인을 추상화하여 의존하게 하면된다.
public static void main(String[] args) {
자동차 자동차 = new 자동차();
인간 _인간 = new 일본인();
자동차.운전하기();
}
public class 자동차 {
public void 운전하기(인간 _인간){
_인간.운전하기();
}
}
public class 일본인 implements 인간{
@Override
public void 운전하기() {
System.out.println("부웅");
}
}
public interface 인간{
void 운전하기();
}
한때 "유지보수 못하게 하는 코드만들기"라는 책이 유명했던 적이 있다. 그 책은 그런 코드를 만들지 말자는게 주 목적이었다.
사람들은 생각보다 유지보수를 중요하게 생각한다. 적당히 돌아가는 코드는 미래의 나를 힘들게 한다는걸 명심하자.
참조 : https://redutan.github.io/2017/06/07/clean-software-part02
https://gall.dcinside.com/board/view/?id=programming&no=2823825&exception_mode=recommend&page=1
댓글을 작성해보세요.