Sangil's blog

https://github.com/ChoiSangIl Admin

[좋은코드, 나쁜코드] 2장 추상화 계층 DEV / PROGRAMING

2023-05-22 posted by sang12


https://github.com/ChoiSangIl/book/blob/main/%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C,%20%EB%82%98%EC%81%9C%EC%BD%94%EB%93%9C/2%EC%9E%A5%20%EC%B6%94%EC%83%81%ED%99%94%20%EA%B3%84%EC%B8%B5.md

코드 작성의 목적은 문제 해결이다.

"결국 코드 작성의 목적은 문제 해결이다. 어떻게 하면 효율적으로 문제를 해결할 수 있는지?
추상화 계층을 통해 설명하려고 하는거 같다. 결국 가독성과 재사용성을 위한게 아닐까"

추상화 계층 및 코드 품질의 핵심 요소

  • 가독성

  • 모듈화

  • 재사용성 및 일반화성

  • 테스트 용이성

"당연한 말... 추상화 하면 좋은점"

API 및 구현 세부사항

어떤 코드를 호출하는 쪽에서 그 코드에 대해 알고 있는 사항을 공개 API라고 생각할 수 있다.
API로 공개되지 않은 내용은 구현 세부사항이다

"결국 공개할 API 는 인터페이스화 시켜 공개하고 구현체에 세부사항을 위임한다.
당연하게도 테스트하기 쉬워지며, 추상화의 이점을 얻어 코드는 이해하기 쉬워진다. 이것또한 당연한 말..."

모든 것을 위한 인터페이스?

  • 장점

    • 퍼블릭 API를 매우 명확하게 보여준다

    • 테스트를 쉽게할 수 있다

    • 쉽게 갈아낄 수 있다. -> 오늘 확신한 것과 한달 후에 확신한 것이 다를 수 있다.

      • 같은 클래스로 두 가지 하위 문제를 해결할 수 있다.

  • 단점

    • 더 많은 작업이 필요하다 (보일러코드)

    • 코드가 복잡해질 수 있다

      • 실제 구현을 파악할 때 구현체를 구현하는 구체클래스를 찾아야 한다.

층이 너무 얇아질 때

= 너무 많은 클래스로 나눴거나, 모든 것을 인터페이스로 만들엇다면..

  • 보일러 코드가 늘어난다

  • 로직을 이해하려면 많은 파일이나 클래스를 따라가야 한다.

  • 인터페이스 뒤에 숨은 구현체들을 파악하기 힘들다 (로직을 이해하거나 디버깅하는 것이 어렵다)

코드를 서로 다른 계층으로 분할해서 얻는 장점과 비교하면 이러한 비용은 상당히 낮은 것이지만 분할을 위한 분할은 의미가 없다는 것을 명심해야 한다.
비용이 이익보다 더 큰 시점이 올 수 있으므로 상식에 맞게 적용하는 것이 좋다.
너무 비대한 계층 때문에 발생하는 문제는 너무 얇은 계층 때문에 발생하는 문제보다 더 심각하다.
확실하지 않은 경우 남용의 위험에도 불구하고 계층을 얇게 만드는 것이 좋다.

"확실하지 않을때면 무조건 계층을 얇게 만들는 것이 좋다? 동의되지 않는다. 결국 그때그때 마다 다르니 잘써라는 말로 들리는건... 기분 탓"

클래스

'클래스는 응집력이 있어야 하고 한가지 일에만 관심을 가져야 한다'와 같은 말에 동의하지 않는 개발자들은 만나보지 못했다.
하지만 이 조언을 알고 있음에도 불구하고 많은 개발자가 여전히 너무 큰 클래스를 작성한다.

느낀점

복잡한 하나의 코드 예시를 제시하고 이를 함수 및 클래스로 분리 하며 최종적으로 인터페이스를 통해 추상화 하며 장점들을 설명한다. 2장에서도 당연한 말들로 시작해 당연한 말로 끝이난다.

  • 하지만 해당 내용을 알고 있음에도 개발자는 여전히 너무 큰 클래스를 작성한다… 이게 2장에서 제일 중요한 말이 아니 였을까?

REPLY

[좋은코드, 나쁜코드] 1장 코드는 어떻게 소프트웨어가 되는가 DEV / PROGRAMING

2023-05-22 posted by sang12


코드 품질의 목표

  1. 작동해야 한다

  2. 작동이 멈춰서는 안된다

  3. 변화하는 요구 사항에 적응해야 한다

  4. 이미 존재하는 기능을 또 다시 구현해서는 안 된다.

"너무 뻔한말이 아닌가..?"

코드는 작동해야 한다

"너무 당연해서 패스"

코드는 작동히 멈추면 안 된다

코드의 작동은 매우 일시적일 수 있다. 왜?

  • 다른 코드에 의존할 수 있는데, 그 코드가 수정되고 변경될 수 있음

  • 새로운 기능이 필요할 때 코드를 수정해야 할 수도 있음

  • 해결 하려고 하는 문제는 시간이 지남에 따라 변경된다. 소비자 선호, 비즈니스 요구, 고려해야 할 기술 등이 바뀔 수 있다.

"여기에서도 의존이라는 말이 나오는구나"

코드는 변경된 요구사항에 적응할 수 있어야 한다.

한 번 작성하고 다시는 수정되지 않는 코드는 거의 없다
왜?

  • 비즈니스 환경이 변한다.

  • 사용자 선호가 변한다

  • 가정이 더 이상 유효하지 않다.

  • 새로운 기능이 계속 추가된다.

적응 가능한 코드를 작성하기 위해 얼마나 많은 노력을 기울여야 할지에 대해 균형 잡힌 결정을 내리는 것은 까다롭다.

"정말 해당 기능이 자주 사용 될것이고 핵심 기능이기 때문에 이정도의 노력을 들여서 개발해야지 라는 결정을 내리는 것은 어려운 것 같다."

"2가지 극단적인 예로 설명하지만 정말 극단적인거 같다."

결국에는 적응성 높은 코드를 작성 해야 된다라는 말

코드는 이미 존재하는 기능을 중복 구현해서는 안 된다

  • 이미 구현한 코드를 재사용하면 좋은 점

    • 시간과 노력을 절약한다

    • 버그 가능성을 줄여준다

    • 기존 전문지식을 활용한다

    • 코드가 이해하기 쉽다

"당연한 말.."

코드 품질의 핵심 요소

  • 코드는 읽기 쉬워야 한다.

  • 코드는 예측 가능해야 한다.

  • 코드를 오용하기 어렵게 만들라.

  • 코드를 모듈화하라.

  • 코드를 재사용 가능하고 일반화할 수 있게 작성하라.

  • 테스트가 용이한 코드를 작성하고, 제대로 테스트하라.

"이건 당연한 말이지만 실제로 실천하기에는 많은 노력이 필요한거 같다."

코드는 읽기 쉬워야 한다.

형편 없이 짜여진 코드를 읽고 무엇에 대한 것인지 알아내려고 노력하는 것은 방금 전 브라우니 레시피(가독성이 낮은 글)를 읽는 경험과 다르지 않다.

우리는 코드를 읽을 때 다음 사항을 이해하기 위해 애쓴다.

  • 코드가 하는 일

  • 어떻게 그 일을 수행하는지

  • (입력이나 상태 등) 어떤 것을 필요로 하는지

  • 코드 실행 결과물

코드의 가독성이 떨어진다면, 다른 개발자가 그 코드를 이해하는 데 많은 시간으 들여야 한다. 또한 코드의 기능을 잘못 이해하거나 중요한 세부사항을 놓칠 가능성 역시 크다.

"가독성이 최고시다"
결국 가독성이 낮으면 코드 검토 중에 버그를 발견할 가능성이 작고, 다른 사람이 수정하면서 버그가 도입될 가능성이 크다.

소프트웨어가 수행하는 모든 일은 그것을 가능하게 하는 어떤 코드에 의해 일어난다!

코드는 예측 가능해야 한다

코드가 아무리 좋은 의도를 가지고 있더라도 예상을 벗어난 동작을 수행하는 것은 위험할 수 있다.
이런 코드는 사소한 오류를 일으킬 수도 있지만, 어떤 경우에는 중요한 데이터가 손상되는 재앙과도 같은 상황을 초래할 수도 있다.
"이것도 당연한 말..."

코드를 오용하기 어렵게 만들라

TV 뒷면을 보면 전원 어댑터와 HDMI 어댑터가 있다.
그런데 TV 제조사에서 이를 다 동일하게 만들면?? HDMI 에 전원 을 꽂았다가 TV가 폭발할 수도 있다.
자신의 코드를 작성할 때 다른 코드가 어떤 것을 꽂기를 기대한다. 즉 호출할 때 인수가 입력되거나 시스템이 특정 상태에 있을 것을 예상한다.
자신이 작성한 코드에 잘못된 것들이 꽂히면, 모든것이 폭발할 수 있다.
"예시가 재밌네"

코드를 모듈화하라

모듈화 되어 있는 코드가 수정하기 쉽다

코드를 재사용 가능하고 일반화할 수 있게 작성하라

테스트가 용이한 코드를 작성하고 제대로 테스트하라.

  • 사람은 실수를 하는 존재다

고품질 코드 작성은 일정을 지연 시키는가?

코드 품질을 고려하지 않고 떠오르는 대로 코딩하면 처음에는 시간을 절약할 수 있다. 그러나 머지 않아 취약하고 복잡한 코드베이스로 귀결될 것이며, 점점 이해하기 어렵고 추론할 수 없는 코드가 된다.
"서두르지 않으면 더 빠르다"

느낀점

"저자는 코드 품질의 핵심 요소에 대해 설명하고 있다. 어찌보면 당연한 이야기로 보이는 말들이 많은데 개발하다보면 이런 기본적인 것들을 생각하지 못할때가 종종 있는거 같다. 다시 한번 생각하고 가자!"

REPLY

코틀린은 어떻게 Java에서 불변객체를 생성할 수 있게 만들었을까? DEV / PROGRAMING

2022-10-22 posted by sang12



“해당 그림으로는 이해가 안돼서 조금 더 뜯어 볼려고?한 이야기“

불변 리스트를 만드는 listOf 함수를 따라가 보자.

public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)

내부적으로 singletonList란 java collection 객체를 리턴 해주는 것을 볼 수 있다. singletonList 함수는 java Collections의 SingletonList<e> Class를 반환한다.
-java Collections.java → SingletonList class

 private static class SingletonList<E>
        extends AbstractList<E>
        implements RandomAccess, Serializable {

        @java.io.Serial
        private static final long serialVersionUID = 3093736618740652951L;

        @SuppressWarnings("serial") // Conditionally serializable
        private final E element;

        ... 생략 ....     
    }
        

여기서 SingletonList는 AbstracList<E>를 상속 받고 있는 것을 볼 수 있다. 어라? 그러면 add를 사용 할 수 있는거 아니야? 라는 의문이 들었다.

직접 사용해보자!

fun main(){
    val americano = Coffee("americano", 5000)
    val mix = Coffee("mix", 300)
    val espresso = Coffee("espresso", 4000)

    //public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)
    val list : List<Coffee> = java.util.Collections.singletonList(americano);
    //list.add() 사용 못함

    var list2 = java.util.Collections.singletonList(americano);
    //list2.add(mix)  //호출가능 하지만 오류가남 listOf는 넘겨주는 객체가 1개일 때는 singletonList를 사용하기 때문에 오류 발생 java singletonList = 인덱스가 1개일인 리스트
}

data class Coffee(
    val name : String,
    val price : Int
)

ArrayList를 사용하는 Immutable List를 선하고 자바 클래스 타입을 찍어보면java.util.Arrays$ArrayList을 사용하는 것을 알 수 있다. 하지만 add를 사용할 수 없다(왜일까? - 뒤에..).

그렇다면 직접 자바의 asList를 만들어서 사용해보자. 그럼 add의 호출은 가능하나 컴파일 시점에서 오류가 난다. 코틀린에서 immutable list에서 여러개의 인자로 리스트를 만들 때 코틀린에서 내부적으로 asList를 이용해 객체를 생성하기 때문에 그렇다. (java의 asList 특성)

fun main(){
    //여러개의 값을 받는 리스트 선언
    val list3 : List<Coffee> = listOf(americano, mix)
    println("${list3.javaClass} $list3") // 출력결과 : class java.util.Arrays$ArrayList [Coffee(name=americano, price=5000), Coffee(name=mix, price=300)] 내부적으로 java ArrayList를 사용하는 것을 알 수 있다.
    //list3.add(espresso) 역시 사용 못함

    val list4 = java.util.Arrays.asList(americano, mix)
    println("${list4.javaClass} $list4")
    //list4.add(espresso) 호출은 가능하지만 오류가남 java의 asList 특성
}

data class Coffee(
    val name : String,
    val price : Int
)

어떻게 코틀린에서는 내부적으로 Java Class를 사용하면서, add 함수를 사용 못하게 만들었을까?
- kotlin lostOf function

public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)
-> val list : List<Coffee> = java.util.Collections.singletonList(americano);

정답은 이 부분에 있는거 같다. 자바의 java.util.Collections.singletonList 객체를 코틀린 List<T> 타입의 형태로 반환 해준다

fun main(){
    //여러개의 값을 받는 리스트 선언
    val list3 : List<Coffee> = listOf(americano, mix)
    println("${list3.javaClass} $list3") // 출력결과 : class java.util.Arrays$ArrayList [Coffee(name=americano, price=5000), Coffee(name=mix, price=300)] 내부적으로 java ArrayList를 사용하는 것을 알 수 있다.
    //list3.add(espresso) 역시 사용 못함

    val list4 = java.util.Arrays.asList(americano, mix)
    println("${list4.javaClass} $list4")
    //list4.add(espresso) 호출은 가능하지만 오류가남 java의 asList 특성
}

data class Coffee(
    val name : String,
    val price : Int
)

어떻게 코틀린에서는 내부적으로 Java Class를 사용하면서, add 함수를 사용 못하게 만들었을까?
- kotlin lostOf function

public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element)
-> val list : List<Coffee> = java.util.Collections.singletonList(americano);

정답은 이 부분에 있는거 같다. 자바의 java.util.Collections.singletonList 객체를 코틀린 List<T> 타입의 형태로 반환 해준다

어떻게 Java 클래스가 코틀린 타입으로 리턴 받을 수 있을까?
→ 코틀린도 결국 Java로 변환 되니 Java의 List와 호환되는 인터페이스로 변환이 되는걸까?
(코틀린에서 Java에 호환되는 인터페이스를 만들어서 사용해보자 요걸 해보면 좀 더 이해가 쉽지 않을까 싶다.. 다음에 해보자 )

처음에 해당 사항이 궁금해서 리서치 중, 코틀린의 List 인터페이스의 구현체가 없을까? 라는 생각으로 접근 했었습니다. 그러다가 찾은 js에서의 ArrayList 구현체.
https://github.com/JetBrains/kotlin/blob/f3385fbdcb8b9dc29661a8a4973c855cdcf73767/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt#L15

kotlin의 List 인터페이스를 보면 add가 없는 것을 볼 수 있다.
결국 코틀린은 Java의 List를 사용하나 코틀린 인터페이스에서 이를 제공해주지 않아서 사용 할 수 없게 만들어 불변(immutable)을 지킨다고 볼 수 있다.

-Collections.kt

public interface List<out E> : Collection<E> {
    // Query Operations

    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>

    // Bulk Operations
    override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean

    // Positional Access Operations
    /**
     * Returns the element at the specified index in the list.
     */
    public operator fun get(index: Int): E

    ....생략 ....
}

1
2023-01-21 01:40:24

1

답글
1
2023-01-21 01:40:32

e

답글
1
2023-01-21 01:40:38

e

답글
1
2023-01-21 01:41:51

e

답글
REPLY