ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 클린 코드 - 객체와 자료 구조
    클린 코드(Clean Code) 2022. 1. 28. 00:01
    728x90

    보통 클래스의 멤버 변수를 private(비공개)로 정의하는 이유는 남들이 변수에 의존하지 않게 만들며 변수를 감추고 싶어서입니다.

     

    그렇다면 과연 getters와 setters 함수를 당연하게 public(공개)하여 비공개 변수를 외부에 노출하는 게 좋을까요?

    아래에 바로 답변이 있습니다.

     

    1. 자료 추상화

    변수 사이에서 함수라는 계층을 넣는다고 구현이 감춰지지않습니다.

    구현을 감추기 위해서는 추상화가 필요합니다.

     

    그저 인터페이스나 getters와 setters 함수로 변수를 다룬다고 클래스 및 추상화가 되지 않습니다.

    사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스입니다.

    개발자는 추상화를 표현하기 위해 고민해야 하며, 아무 생각 없이 getters/setters 함수를 추가하는 방법은 나쁩니다.

     

    코드를 통해 조금 더 살펴보겠습니다.

    public interface Vehicle {
    	double getFuelTankCapacityInGallons();
    	double getGallonsOfGasoline();
    }

    위의 코드는 자동차 연료 상태를 구체적인 숫자 값으로 알려줍니다.

    즉, 두 함수가 그저 변숫값을 읽어 반환합니다.

     

    public interface Vehicle {
    	double getPercentFuelRemaining();
    }

    위의 코드는 자동차 연료 상태를 백분율이라는 추상적인 개념으로 알려줍니다.

    즉, 정보가 어디서 오는지 전혀 드러나지 않습니다. (진정한 의미의 클래스)

     

    2. 절차적인 코드 vs 객체적인 코드

    //절차적인 코드
    
    public class square {
    
    }
    
    public class Circle {
    
    }
    
    public class Geometry{
    
    	public double area(Object shape){
    		if(shape instanceof Square){
            	return 정사각형 넓이 구하는 연산;
            	}
            	else if (shape instanceof Circle){        
            		return 원 넓이 구하는 연산;
            	}
    	}
    }

    위의 코드에서 둘레 길이를 구하는 perimeter() 메서드를 추가하고 싶다면? 도형 클래스(square, Circle)는 아무런 영향을 받지 않는다.

    하지만 새로운 도형(클래스)을 추가하고 싶다면..? Gemotry 클래스에 속한 함수를 모두 수정해야 합니다.

     

    //객체적인 코드
    
     public class Square implements Shape {
     	public double area(){
     	   	return 정사각형의 넓이를 구하는 연산;
        }
     }
     
     
     public class Circle implements Shape {
     	public double area(){
        		return 원의 넓이를 구하는 연산;
        }
     }

    위의 코드에서 Gemoetry 클래스는 필요 없고, 새로운 도형을 추가해도 기존 메서드에 아무런 영향을 미치지 않습니다.

    하지만 새로운 메서드를 추가하고 싶다면 도형 클래스를 전부 고쳐야 합니다.

     

    정리

    절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.

    하지만 절차적인 코드는 새로운 자료 구조를 추가하기 어렵다.

     

    객체적인 코드는 기존 함수를 변경하지 않으면서 새 클래스(자료 구조)를 추가하기 쉽다.

    객체적인 코드는 새로운 함수를 추가하기 위해서는 모든 클래스를 고쳐야 한다.

     

    즉, 객체적인 코드에서 어려운 변경은 절차적인 코드에서 쉬우며 절차적인 코드에서 어려운 변경은 객체적인 코드에서는 쉽습니다.

     

    따라서 모든 것이 객체라는 생각보다는 상황에 따라 적절한 코드를 선택하는 것이 좋습니다.

     

     

    3. 디미터의 법칙

    Demeter라는 프로젝트를 진행하던 개발자들은 어떤 객체가 다른 객체에 대해 지나치게 많이 알다 보니, 결합도가 높아지고 좋지 못한 설계를 야기한다는 것을 발견하였다. 그래서 이를 개선하고자 객체에게 자료를 숨기는 대신 함수를 공개하도록 하였는데, 이것이 바로 디미터의 법칙이다.

    디미터의 법칙은 " 클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다"라고 주장합니다.

    - 클래스 C의 메서드

    - 클래스 C의 메서드 f가 생성한 객체

    - 클래스 C의 메서드 f의 인자로 넘어온 객체

    - 클래스 C의 멤버 변수에 저장된 객체

     

    하지만 위 객체에서 허용된 메서드가 반환하는 객체의 메서드는 호출하면 안 됩니다.

     

     

    그러면 아래의 코드는 디미터의 법칙을 위배할까요?

    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

    법칙을 위배하는지 살펴보기 전에

    위와 같은 코드를 여러 객차가 한 줄로 이어진 기차처럼 보이기 때문에 흔히 기차 충돌(train wreck)라고 부릅니다.

     

    아래의 코드처럼 나누면 좋습니다.

    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    final String outputDir = scratchDir.getAbsolutePath();

    이제 디미터의 법칙을 위배하는지 확인해보겠습니다. 

    위의 코드가 디미터의 법칙을 위배하는지는 ctxt, Options, ScrathDir이 객체인지 자료구조인지에 달렸습니다.

    만약 객체라면 내부 구조를 숨겨야 하므로 디미터 법칙을 위배한다.

    만약 자료 구조라면 당연히 내부 구조를 노출하므로 디미터 법칙을 위배하지 않습니다.

     

    이렇게 객체인지 자료구조인지 헷갈리는 이유는 조회 함수(get~~)를 사용했기 때문입니다.

     

    코드를 아래와 같이 구현했다면 해결됩니다.

    final String outputDir = ctxt.options.scratchDir.absolutePath;

    자료 구조는 무조건 함수 없이 공개 변수만 포함하고 객체는 비공개 변수와 공개 함수를 사용한다면 디미터 법칙을 고민할 필요가 없습니다.

    하지만 단순한 자료 구조에도 getters와 setter를 정의하라는 프레임워크와 표준이 존재합니다.

     

    이런 혼란으로 인하여 때때로 절반은 객체, 절반은 자료 구조인 잡종 구조가 나옵니다.

    이러한 잡종 구조는 새로운 함수, 새로운 자료구조도 추가하기 힘들기 때문에 양쪽에서의 단점만 모아놓은 구조로 되도록 피해야 합니다.

     

    만약 ctxt, options, scratchDir이 객체라면 어떻게 해야 할까요?

    앞선 코드와는 달리 객체는 내부 구조를 감춰야 하므로 줄줄이 사탕으로 엮으면 안 됩니다.

    ctxt가 객체라면 뭔가를 하라고 말해야지 속을 드러내라고 말하면 안 됩니다.

     

    ctxt.getAbsolutePathOfScratchDirectoryOption();
    
    ctx.getScratchDirectoryOption().getAbsolutePath();

     

    첫 번째 방법은 ctxt 객체에 공개해야 하는 메서드가 너무 많아지며 두 번째 방법은 객체가 아닌 자료구조를 반환한다고 하더라도 썩 내키지 않는다.

     

    ScratchDirectory(임시 디렉터리)의 절대 경로가 왜 필요할까를 찾아보았을 때 임시 파일을 생성하기 위한 목적이라는 사실이 드러났습니다.

     

    그래서 ctxt객체에게 임시 파일을 생성하라고 시켜야 합니다.

    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

    이를 통해 ctxt는 내부 구조를 드러내지 않으며 모듈에서 해당 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없어지고 디미터 법칙을 위반하지 않습니다.

     

     

     

    출처

    클린 코드(로버트 C. 마틴)

    https://mangkyu.tistory.com/147

     

    [OOP] 디미터의 법칙(Law of Demeter)

    1. 디미터의 법칙(Law of Demeter) [ 디미터의 법칙(Law of Demeter) 이란? ] 디미터의 법칙은 “Object-Oriented Programming: An Objective Sense of Style” 에서 처음으로 소개되었다. Demeter라는 프로젝트..

    mangkyu.tistory.com

     

    댓글

Designed by Tistory.