본문 바로가기
CS/Python&R

프로그래머를 위한 파이썬

by Diligejy 2022. 7. 4.

p.6

설계(Design)라는 단어는 종종 가시적인 결과물이라고 하지만 설계의 진정한 가치는 어떤 결과에 도달하는 과정(process)에 있다.

 

p.8

여러분이 회사의 보고용 소프트웨어를 업데이트하는 작업을 맡았다고 가정하자. 현재는 익스포트 파일로, 쉼표로 값을 구분하는 데이터(csv)를 사용한다. 하지만 사용자는 탭으로 값을 구분하는 데이터(TSV)가 좋다고 말한다. 그래서 여러분은 '쉼표 대신 탭으로 구분하도록 업데이트해야지.'라고 마음먹었다. 그런데 출력하는 코드를 열어보니 다음과 같다.

 

print(col1_name + ',' + col2_name + ',' + col3_name + ',' + col4_name)
print(first_val + ',' + second_val + ',' + third_val + ',' + fourth_val)

 

CSV를 TSV로 바꾸기 위해서 6곳의 쉼표를 탭으로 바꿔야 한다. 사람인지라 실수할 수도 있을 것이다. 예를 들어, 첫 번째 줄의 헤더를 출력하는 부분을 봤지만 놓치고 지나칠 수도 있다. 이를 방지하려면 이 코드를 사용하게 될 다른 개발자를 위해 구분 값을 상수에 저장하여 필요할 때 사용할 수 있게 만들 수 있다. 또한, 파이썬 함수를 사용하여 문자열을 더 쉽게 만들 수 있다. 이렇게 하면 사용자가 탭보다 쉼표를 좋아한다고 최종 결정되더라도 한 곳만 수정하면 된다.

 

DELIMITER = '\t'
print(DELIMITER.join([col1_name, col2_name, col3_name, col4_name])
print(DELIMITER.join([first_val, second_val, third_val, fourth_val])

 

작업을 멈추고 시스템을 조금 더 깊이 생각해 보면, 이전에는 생각지도 못한 투박한 부분이 보이거나 어떤 가정이 정밀하지 않다는 것을 깨닫게 된다. 이런 과정에서 스스로 놀라게 될 것이며 이러한 발견은 여러분에게 작업을 계속할 수 있는 동기부여가 될 것이다. 반복되는 패턴과 일반적인 실수를 발견하기 시작하면, 어디를 고쳐야 하는지 알게 된다. 이것은 상당히 효과적일 것이다.

 

p.26

파이썬에서는 from 모듈명 import * 를 사용하면 모듈에 있는 모든 이름을 임포트할 수 있게 해준다. 이렇게 하면 코드에서 '모듈명.'식의 접두어를 쓰지 않아도 되기 때문에, 이렇게 하고 싶은 유혹이 생기겠지만, 절대로 이렇게 하지 마라! 와일드카드를 사용하는 임포트는 이름 충돌을 일으킬 수 있으며, 임포트된 이름을 명시적으로 볼 수 없기 때문에 디버깅하기도 어렵다. 항상 명시적으로 임포트하자.

p.28

확실한 이유가 없다면 파이썬의 내장 함수를 오버라이드하지 말자. 다시 말해, 오버라이드 하지 않을 것이라면 내장 함수와 같은 이름을 사용하지 않는 것이 최선이다. 하지만 파이썬의 모든 표준 라이브러리를 다 모른다면, 본인도 모르게 내장 함수와 같은 이름을 사용하게 될 가능성은 여전히 존재한다. 여러분이 만든 모듈을 다른 모듈에서 임포트시킬 때 새로운 이름(alias)를 주는 방법도 있다. 하지만 여러분이 만든 모듈의 이름을 바꾸고(rename) 전체 코드에서 그 모듈을 참조하는 부분을 찾아 바뀐 이름으로 업데이트하는 것을 권장한다. 이렇게 하면 임포트에 사용하는 이름과 실제 모듈의 이름에 대한 일관성을 유지할 수 있다.

 

이러한 임포트 방법을 사용하면 필요한 것들을 문제없이 가져올 수 있다. time. 또는 datetime. 처럼 모듈명을 접두어로 사용하면 네임스페이스의 충돌 없이 작업할 수 있게 도와줄 것이다. 충돌이 발생하면, 크게 한 번 심호흡하고 임포트 구문을 재작성하거나 새로운 이름(alias)을 생성하자!

 

p.28

관심사 분리를 잘하는 한 가지 방법은 '하나의 작업을 하되 그것을 잘하는 프로그램을 만들어라'라는 유닉스 철학을 따르는 것이다. 코드상 특정 함수나 클래스가 단일 동작과 관련되었다면, 그것을 사용하는 코드와는 상관없이 해당 함수 또는 클래스를 개선할 수 있다. 반대로, 어떤 동작들이 전체 코드 내에서 복제되었거나 동작들끼리 혼합되었다면, 연관된 다른 동작을 고려하지 않고서는 개선하기 힘들 것이다. 최악의 경우, 완전히 분해해서 다시 만들어야 할 수도 있다. 예를 들어, 웹사이트 상의 많은 함수가 현재 인증된 사용자 정보에 의존한다고 해보자. 그 함수들 모두는 인증을 확인하고 해당 사용자의 정보를 가져온다면 사용자 인증에 대한 세부 사항이 변경될 때마다 모두 업데이트되어야 할 것이다. 이것은 정말 많은 작업이며 하나의 함수라도 업데이트하지 않는다면 예상치 못한 일이 발생하거나 심각할 경우 프로그램 전체가 작동하지 않게 될 수도 있다.

 

p.33

def join_names(names):  # <1>
    name_string = ""

    for index, name in enumerate(names):
        if index > 0 and len(names) > 2:
            name_string += ","
        if index > 0:
            name_string += " "
        if index == len(names) - 1 and len(names) > 1:
            name_string += "and "
        name_string += name
    return name_string


def introduce(title, names):  # <2>
    print(f"{title}: {join_names(names)}")

introduce함수는 그렇게 많은 작업을 하지 않기 때문에 이 코드는 과도한 것처럼 보인다. 하지만 각 관심사를 함수로 분리하는 이런 식의 분해는 나중에 버그를 수정하거나 기능을 추가하고 코드를 테스트할 때 도움을 준다. 만약 멤버들의 이름을 결합하는 방식에 버그가 생겼다면, join_names 함수를 만들기 이전의 introduce 함수 속에서 버그를 찾기보다, 지금처럼 만든 join_names 함수에서 수정할 코드를 찾기가 더 쉽다.

댓글