요즘은 코드를 잘 짠다 = 읽기가 좋다 = 가독성 있다 , 라는 말을 많이 하는 것 같습니다.

저 또한 매우 동의하는 말인데요 하지만 개인적으로 "가독성 있다"라는 말 또한 추상적인 개념이라 생각합니다.
가독성 있는게 좋아? , 그럼 어떤 코드가 가독성 있는건데? 라는 질문을 받게 되는 것이죠

그래서 우리는 "가독성 있는 것"에 대한 기준이 필요한데 저에게는 "직관적이냐" 입니다.

다음은 제가 좋아하는 3-Tier Architecture인데요 이것을 처음 봤을 때 느낀것은 정말 직관적이다 였습니다.

계층과 책임이 너무 명확히 나눠져있어서 누가 봐도 이해가 가능한, 이런게 "직관적이다" 라고 생각하고
이런 직관적임을 코드레벨까지 성공적으로 가져 왔을 때 곧 읽기좋은 코드가 된다 생각합니다.

 

 

 


저는 추상화를 이렇게 정의합니다,  무언가를 간추린다.
무언가를 간추린다면 전부 추상화에 해당하며 적용되는 범위또한 굉장히 넓다 생각합니다. 

예를 들어 우리가 "탈 것"이라는 인터페이스를 만든다면 이는 추상화 정도가 굉장히 높은 타입을 설계하는 것과 동일 하거든요
"탈 것"이라는 타입은 추상화 정도가 너무 높아 , 구체화가 없고 단순히 명시만 돼있는 Specification의 역할만 하는 것이죠.

단순히 말하면 야 너는 어떻게 하는지는 너 마음인데 , 가속이랑 감속은 가능해야 한다? 같은 것이죠 
따라서 구체화된 코드 없이 메서드의 Spec만 명시합니다.

"탈 것"을 구현한 "자동차"라는 타입을 만들고 이에 대한 api를 제공한다면 이 또한 추상화입니다.
api라는 통로를 이용해 외부에서 이 자동차라는 타입이 어떻게 설계되고 내부적으로 어떻게 동작하는지를 몰라도 
사용할 수 있게 됐기 때문입니다.

우리가 docker run 명령어를 사용했을때 내부적으로 어떻게 동작하는지 정확한 이해와 함께 쓴다고 생각하진 않습니다.
아 이 명령어는 = 이 api는 이런 동작을 하는 구나~ 정도의 이해만 가지고 있는 것이죠 

이렇게 api는 외부에서 접근 가능한 일종의 추상계층으로서 역할을 하게 됩니다.

저는 정보를 숨기는 것 , 절차를 간추리는 것 , 데이터를 간추리는 것.... 이 모든 것이 추상화의 영역이라 생각합니다.

마침 1년 전쯤에 추상화에 대한 글을 작성한 적이 있는데요 많이 부족한 글이지만 첨부해봅니다.

 

Abstraction(추상화) 기본 개념 - 1편

Abstraction(추상화)란? 자바에서는 추상클래스 , 추상메서드 , 추상화 등 "추상"이라는 말이 자주 쓰입니다. 그렇다면 추상화란 무엇일까요? 추상화는 프로그래밍에서 매우 중요한 개념 중 하나이

masiljangajji-coding.tistory.com

 

 

 

제가 이번 세션에서 가장 인상깊게 들었던 부분은 추상화 레벨인데요 


같은 세계에서는 추상화의 정도가 같아야 한다. 라는 의미입니다.
와 이건 정말 생각하지 못했던 부분이라 놀랐는데요 

이런 피드백을 한번쯤은 들어보셨을 겁니다.

"메서드 여러개로 분리해라" 

이유는 간단한데요 거대한 기능을 하나의 메서드로만 관리하면 너무 많은 책임을 갖게 됩니다.
기능이 고도화 될 수록 메서드는 더욱 거대해지고 이는 직관적으로 표현하기가 어려짐을 의미합니다.

따라서... 책임의 분리 -> 직관적인 표현 + 유지보수의 편의성을 위해 메서드를 분리 하는 것인데요

하지만 "그럼 정말 모든 메서드를 기능별로 하나하나씩 쪼개서 최대한 작은 단위로 만들어야 하나?" 라는 질문을 한다면  
저는 아니요 라는 답변을 할 것입니다.

여러 행위를 한다 해도 그것이 하나의 주제와 문맥으로 설명 가능하다면 하나로 만들어도 된다 생각합니다.

왜냐하면 메서드가 많아지는 것도 직관적인 것과는 거리가 멀어지기 때문입니다.
메서드를 분리한다는 것은 결국 코드의 양이 늘어난다는 것이며 누군가가 내 코드를 다룰 때 찾아봐야 하는 부분 또한 늘어납니다.
따라서 마냥 쪼개는 것이 좋다고는 생각 안합니다.
 


그리고 이런 고민은 커밋을 할때도 똑같이 발생 하는데요
커밋도 한번에 많은 양을 올리지 마라 , 쪼개서 하나의 단위씩 올려야 좋다. 라고 말하곤 합니다.
그럼 정말 하나의 코드 변경 = 1커밋 이여야 할까? , 만약 우리가 리펙토링을 통해서 이름을 변경했다고 했을 때
A이름변경 , B이름변경 , C이름변경 ......... 이렇게 이름변경에 관한 커밋을 수십개씩 쪼개놓았다면 어떨까요?

코드를 리뷰하려고 봤는데 커밋이 수십개씩 쌓여있으면 리뷰하기가 겁나는 경험이 있으실 겁니다.
설령 그것이 큰 내용이 없는 코드라 할지라도 너무 잘게 쪼개져있으면 집중력있게 리뷰하는 것은 어려워 집니다.
중요한 변경사항이 아니라면 차라리 묶어서 표현하는게 좋지 않을까요? User 관련 객체 이름변경.. 등으로

 

이렇게 되면 머리가 아파집니다. 무작정 쪼갤수도 , 무작정 합칠수도 없다면 무엇을 기준으로 쪼개고 합쳐야 하나?? 라는 것이죠
이 추상화 레벨에 대한 것은 판단에 대한 하나의 기준이 될 수 있다 생각합니다.

-> 지금 얘가 가지고 있는 추상화 레벨의 정도가 주변의 것들과는 조금 다른거 같은데??
-> 위에는 전부 강하게 추상화하여 구체화를 숨기고 명시적으로 어떤 기능을 할 것임만을 말했는데 갑자기 구체화가 등장하네??
-> 아 이거는 메서드 분리 해야겠다

와 같은 흐름을 통하는 것이죠 , 이 부분에 대해서는 기존에 생각했던 것이 아니라 참 인상깊은 부분이였습니다.

'클린코드' 카테고리의 다른 글

그래서 클린코드가 뭐냐  (2) 2024.10.06
클린코드&테스트 - 객체지향과 SOLID  (2) 2024.10.03

Why Abstraction??

우리는 이전의 글을 통해 Abstraction의 개념을 간략하게 이해했습니다.

 

Abstraction(추상화) 기본 개념 - 1편

Abstraction(추상화)란? 자바에서는 추상클래스 , 추상메서드 , 추상화 등 "추상"이라는 말이 자주 쓰입니다. 또한 Abstract으로 선언된 클래스 , 메서드 등을 보고 추상화시켰다 합니다. 그렇다면 추상

masiljangajji-coding.tistory.com

추가적으로 다음 글을 읽는 것을 추천드립니다.

 

Specification 기본 개념 및 활용

Specification 프로그램에서 터지는 버그는 대부분 동작에 대한 오해로 발생합니다. 이러한 동작의 오해를 줄이기 위한 대표적인 방법으로 Spec(명세)이 존재합니다. 또한 완성된 프로그램은 필수적

masiljangajji-coding.tistory.com

 

이 글에서는 이전 글과 비슷한 내용을 다루되 더욱 세세한 부분을 보려고 합니다.


Abstraction을 하게되면 얻을 수 있는 핵심 이점은 다음과 같습니다.

  1. 시스템을 구성 요소 또는 모듈로 나누어 재사용할 수 있다록 한다.(Modularity 모듈성)
  2. 모듈 주의에 벽을 구축하여 모듈이 자체적으로 책임지고 시스템의 다른 부분에서 발생한 버그가 모듈의 무결성을 손상시킬 수 없도록 한다.(Encapsulation 캡슐화)
  3. 모듈의 구현 세부사항을 숨겨 세부사항을 변경해도 시스템의 나머지 부분을 변경하지 않아도 된다. (Imformation Hiding)
  4. 기능을 모듈의 책임으로 만들어 여러 모듈에 걸쳐 분산되지 않도록 한다. (Separation of Concerns 관심사분리)

어떻게 이런 일이 가능한 것일까요??

 

자바에서 제공하는 List에 대해서 보겠습니다.

ArrayList , LinkedList 둘 다 List Interface를 상속받아 구현하는 방식으로 설계돼있습니다.

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList

 

 

지금부터 이러한 구조의 이점을 알아보겠습니다.

 

List Interface

List라는 타입이 필수적으로 가져야 하는 연산을 명시한 것입니다.
연산에대한 간단한 Specification을 명시한 것이죠

 

따라서 우리는 앞으로 List라는 타입이 가져야하는 연산을 알 수 있습니다.

(List에 명시돼있는 size , isEmpty , contains 연산의 Specification)

 

사용자는 List의 구현체를 보지 않아도 , 즉 내부적인 구현이 어떤식으로 이루어져있는지 몰라도
간단한 Spec만으로 충분한 사용이 가능해집니다. (Imformation Hiding)

 

사용자가 List 내부구현에 의존하지 않고 사용하기 떄문에 갑작스럽게 List 구현체의 내부구현을 바꾸더라도
아무런 문제가 발생하지 않을 것 입니다. (Encapsulation)

 

Spec을 명시함으로써 자연스럽게 연산의 구조를 알 수 있게됐고 이를통해 세세한 메서드 분리가 가능해집니다.
이는 PreCondition , PostCondition 같은 조건들을 제공하고 Unit Test를 돕습니다.

 

후에 배열구조나 노드구조를 사용하지 않는 새로운 구조의 List를 만든다고 했을때도
List Interface를 재사용 함으로써 코드의 재사용성을 증가시킬 것 입니다. (Modularity)

 

List이외에도 Collection FrameWork 안에있는 Abstract Data Type은 Interface를 두고 구현체를 따로 두는 방식을 채택합니다.

 

 

이러한 방법은 쓸대없는 일을 여러번 하는 것 처럼 보이지만 실제로는 위에서 기술 한 것처럼 많은 이점이 있는것이죠

 

Collection 의 Super Type으로 Iterable가 존재하는 것도 같은 이치입니다.

 

계속해서 강조하자면 Abstraction은 매우 큰 범위를 갖고있는 말입니다.

 

사용자에게 내부구현 정보를 숨기는 것 ... Abstraction

새로운 구조를 갖는 List가 필요할때 implements 받아 연산 정의 절차를 줄이는 것 ... Abstraction

 

무언가를 "간추린다" 면 전부다 Abstraction에 해당하기 떄문이죠 

 

이 글에서는 Abstraction이 갖는 이점과 보다 세세한 관점에 대해서 다뤘습니다.
도움이 되셨다면 좋겠습니다.

Abstraction(추상화)란?

 

자바에서는 추상클래스 , 추상메서드 , 추상화 등 "추상"이라는 말이 자주 쓰입니다.

 

그렇다면 추상화란 무엇일까요?

 

추상화는 프로그래밍에서 매우 중요한 개념 중 하나이며 매우 큰 범위를 지칭하는 말로

데이터나 절차를 단순하게 표현하여 간추리는 것을 의미합니다.

 

또한 Abstraction을 추상화라고 직역하는 것도 상당히 어폐가 있음으로 추상화를 전부 Abstraction으로 지칭하겠습니다.

이제 Abstraction을 크게 2가지로 포괄적으로 이해해보겠습니다.

 

1. Data를 간략화 시킨 Data Abstraction

2. 절차를 간략화 시킨 Procedure Abstraction

(단순히 코드를 Extends하여 상속받는 것 또한 Code Abstraction에 속합니다.)

 

Data Abstraction

 

Data Abstraction은 Data를 다루고 저장하는 방법을 간략화 시킨 것 입니다.

예시로는 Array , List , Stack , Queue 와 같은 자료구조들이나 사용자가 정의하는 타입이 이에 해당합니다.

 

Stack과 Queue에 가장 큰 특징은 각각이 FILO , FIFO 구조를 띈다는 것입니다.

이 규칙을 지키기만 한다면 내부적으로는 Array로 구현하든 List로 구현하든 상관없이 Stack 과 Queue라는 자료구조로 지칭됩니다.

 

또한 Set처럼 중복을 허용하지 않는 구조나 Map처럼 특정 Key를 통해 1:1 Mapping되는 구조등 다양한 구조가 존재합니다.

이러한 자료구조는 Data를 다루는 방식을 명시하고 규칙으로 지정한 것입니다.

 

또 이러한 규칙을 가진 구조를 단순히 Stack , Queue 등의 타입으로 간략화 시켜 사용가능하게 만든것을 Data Abstraction이라 합니다.

 

이 구조가 내부적으로 어떻게 구현됐는지 몰라도 간단한 사용법만 알면 충분히 사용하게 만드는 것이죠.

 

만약 프로그램이 Set이라는 자료구조로 작성이 됐다면
프로그램의 코드를 확인하지 않더라도 중복을 허용하지 않는 프로그램이구나 ! 라고 충분히 유추가 가능합니다.

 

이렇게 사용자가 정확한 내부의 구현을 모르더라도 충분히 사용이 가능하고 유추가 가능하게 만드는 것 또한 Abstraction 이라 합니다.

 

Procedure Abstraction

 

Procedure Abstraction은 절차를 간략화 시키는 것입니다.

 

절차를 간략화 시킨다는 것은 사용자가 수동으로 처리해야 할 부분을 프로그래밍 언어에서 자체적으로 처리하게 만들거나
기능이 비슷한 연산들을 묶어서 하나로 표현하는 것을 의미합니다.

 

간단한 사칙연산 프로그램을 예시로 들어보겠습니다.

public class Main {

    public static void main(String[] args) {


        int number1 = 100;
        int number2 = 5;

        System.out.println(plus(number1, number2));
        System.out.println(minus(number1, number2));
        System.out.println(multiply(number1, number2));
        System.out.println(divide(number1, number2));

    }

    public static double plus(int a, int b) {
        return a + b;
    }

    public static double minus(int a, int b) {
        return a - b;
    }

    public static double multiply(int a, int b) {
        return a * b;
    }

    public static double divide(int a, int b) {
        return a / b;
    }

}

간단한 사칙연산 프로그램이며 큰 문제는 없어 보입니다.

하지만 자세히 보면 메서드들의 파라미터와 반환 타입이 동일합니다.

 

이점을 이용해 사칙연산 코드를 하나의 연산으로 묶는 Procedure Abstraction을 해보겠습니다.

SubTyping 을 이용한 Procedure Absraction

public class Main {

    public static void main(String[] args) {


        int number1 = 100;
        int number2 = 5;

        System.out.println(calc(new Add(), number1, number2));
        System.out.println(calc(new Minus(), number1, number2));
        System.out.println(calc(new Multi(), number1, number2));
        System.out.println(calc(new Divide(), number1, number2));

    }

    public static double calc(Calculator calculator, int a, int b) {
        return calculator.calc(a, b);
    }
}

class Add implements Calculator {

    @Override
    public double calc(double a, double b) {
        return a + b;
    }
}

class Minus implements Calculator {

    @Override
    public double calc(double a, double b) {
        return a - b;
    }
}

class Multi implements Calculator {

    @Override
    public double calc(double a, double b) {
        return a * b;
    }
}

class Divide implements Calculator {

    @Override
    public double calc(double a, double b) {
        return a / b;
    }
}


@FunctionalInterface
interface Calculator {
    double calc(double a, double b);
}

Calculator 라는 Interface를 지정한 후 implements 를 이용해 SubType관계를 만들어 줬습니다.

 

이 관계를 이용해 4개의 메서드로 분리돼있던 기능이 calc 메서드 하나로 묶인것을 볼 수 있습니다.

이런식으로 공통된 부분을 묶어주어 간추리는 방식을 Procedure Abstraction이라 합니다.

 

실제로 SubTyping 을 이용하는 테크닉은 매우 효율적이지만 이 예시에서는 클래스를 여러개 만들어 코드의 양이 많아지고

복잡해 보이는 문제점을 갖게됐습니다. 좋은 Procedure Abstraction은 아닌 것 이죠

 

따라서 이 코드를 다른 방법으로 Abstraction 하겠습니다.

 

Lambda 를 이용한 Procedure Abstraction

public class Main {

    public static void main(String[] args) {


        int number1 = 100;
        int number2 = 5;

        System.out.println(calc((a, b) -> a + b, number1, number2));
        System.out.println(calc((a, b) -> a - b, number1, number2));
        System.out.println(calc((a, b) -> a * b, number1, number2));
        System.out.println(calc((a, b) -> a / b, number1, number2));

    }

    public static double calc(Calculator calculator, int a, int b) {
        return calculator.calc(a, b);
    }
}


@FunctionalInterface
interface Calculator {
    double calc(double a, double b);
}

똑같은 역할을 하는 프로그램이지만 Lambda식을 사용함으로써 연산 절차를 간추리는 Abstraction과 코드의 재사용성을 늘려줬습니다.

 

이 글에서는 Abstraction에 대해서 간략하게 알아 봤습니다.

 

Abstraction은 프로그래밍을 더욱 효율적으로 만들어주는 중요한 원칙 중 하나입니다 .

또한 매우 큰 범위를 지칭하는 말이기 떄문에 이 글에서 사용된 것 외에도 더욱 많은 예시가 존재합니다.

 

더 많은 이점을 알고 싶다면 다음을 추천합니다Abstraction(추상화) 기본 개념 - 2편

 

Abstraction(추상화) 기본 개념 - 2편

Why Abstraction?? 우리는 이전의 글을 통해 Abstraction의 개념을 간략하게 이해했습니다. Abstraction(추상화) 기본 개념 - 1편 Abstraction(추상화) 기본 개념 - 1편 Abstraction(추상화)란? 자바에서는 추상클래스

masiljangajji-coding.tistory.com

 

도움이 되셨다면 좋겠습니다.

+ Recent posts