최근에 다양한 데이터 분석이나 데이터의 매칭을 위해서 문자열 처리나 SQL 관련 그리고 다양한 데이터 프로세싱 함수를 많이 작성하고 있다. Seed 단계 스타트업의 바쁜 일정 속에서 좋은 코드를 작성하는 것은 항상 어렵고 도전적인 일인 것 같다. 기능이 working한 후에 다시 코드를 보면 코드가 마음에 드는 경우가 거의 없다. 조금 더 의식적으로 좋은 코드를 작성하려고 노력해야 할 것 같다. 평소에 가지고 있던 생각을 기반으로 GPT를 사용해서 정리해보려고 한다. 좋은 함수를 작성하는 방법에 대한 글을 먼저 작성하고, 다음 포스팅으로 파이썬에서 좋은 함수를 작성하는 법에 대해서 한 번 정리해보려고 한다. 다음 포스팅에서는 파이썬의 언어적 특성을 고려하면서 조금 더 커스터마이징된 구체적인 방법을 정리해놓고 리마인드 할 예정이다.
좋은 코드
먼저 좋은 함수에 대해서 이야기 하기 전에 내가 생각하는 좋은 코드에 대해서 정리해보면 아래와 같다.
내가 생각하는 좋은 코드는 크게 3가지 측면에서 뛰어난 코드다.
1. 가독성: 읽기 쉬운 코드. 읽기 쉬운 코드는 같이 개발하는 팀원들의 생산성을 높이기 때문에 정말 중요하다. 코드는 계속 변경되는 설계도와 같기 때문에 변경하거나 문제를 찾아야 할 때는 항상 읽어야 하기 때문에 프로젝트가 어느 정도 규모가 되면 코드를 이해하고 파악하는데 많은 시간을 들이기 때문이다. 가독성이 좋은 코드는 일관성이 있고, 잘 나눠져(모듈화) 있고, 함수나 변수의 이름이 이해하기 쉽고 간견해야 한다.
2. 성능: 아무 가독성이 좋고, 테스트 하기 쉬운 코드라도 프로젝트에서 요구하는 성능에 충족하지 않으면 좋은 코드가 아니다. 그렇기 때문에 자신이 작성하는 코드가 컴퓨터나 프로그램에서 어떻게 동작하는지 이해해야한다. 이 부분이 컴퓨터 사이언스를 열심히 공부해야 하는 이유다.
예시) 아래 두 코드는 같은 역할을 하지만, List Comprehension을 사용한 코드가 for 루프를 사용한 코드보다 약 2배 더 빠릅니다. List Comprehension은 내부적으로 C 코드로 구현되어 있어서 더욱 빠르게 실행됩니다.
# list comprehension
squares = [x for x in range(100000)]
# for loop
squares = []
for x in range(1000000):
squares.append(x**2)
3. 테스트 : 좋은 코드는 테스트가 쉬워야 합니다. 테스트가 쉬운 코드는 에러를 발견하기 쉽고, 새로운 코드를 추가할 때도 기존 코드가 잘 동작하는 것을 보장해서 생산성에 큰 도움을 줍니다. 예를 들면 데이터 엔지니어링에서는 외부와 연결(IO가 발생하는)되는 부분을 함수로 분리하면 테스트 코드를 쉽게 작성할 수 있습니다.
# s3에서 데이터를 읽어오는 함수
def extract():
df = read_parquet_from_s3(path)
return df
def read_parquet_from_s3(path) -> pandas.DataFrame:
df = pandas.read_parquet(path)
return df
# 위와 같이 데이터를 읽어오는 부분을 함수로 분리하면
# read_parquet_from_s3 함수만 mocking하면 외부와의 연결 없이도 테스트가 가능해짐.
좋은 함수
함수도 결론은 코드이기 때문에 위에 3가지를 잘 지킨 코드가 좋은 함수일 것이다. 하지만, 함수의 구성(function name, argument, return) 과 특성에 맞게 설명하면 아래와 같다.
1. 목적과 기능을 명확하게 정의하기: 함수를 만들기 전에, 함수가 어떤 목적과 기능을 수행하는지 명확하게 정의하는 것이 중요하다. 이를 통해 함수의 이름, 매개변수, 반환값 등을 결정하고, 함수를 작성할 때 목표를 더 잘 이해할 수 있습니다.
# 숫자 리스트에서 최댓값을 반환하는 함수
def find_max(numbers: List[int]) -> int:
...
2. 함수의 매개변수와 반환 값을 잘 정의하기: 입력과 출력에 대한 정확한 값을 정의하고, naming함으로써 가독성을 높일 수 있다.
# 두 개의 숫자를 더한 결과를 반환하는 함수
def add_numbers(num1: int, num2: int) -> int:
return num1 + num2
3. 예외 상황을 잘 처리하기 : 이를 통해서 사용자(내가 만든 함수의 사용자)가 알 수 없는 에러를 겪는 수를 줄일 수 있다. 미리 예외사항을 처리하는 것이 좋다.
def find_max(numbers):
if not numbers:
raise ValueError("Empty list")
max_num = numbers[0]
for num in numbers[1:]:
if num > max_num:
max_num = num
return max_num
4. 함수의 부작용: 함수는 종종 변수를 수정하거나 다른 상태를 변경한다. 특히, mutable 객체를 argument로 사용하면 이런 경우가 발생할 수 있다. 이러한 상태 변경을 함수의 부작용이라고 한다. 함수의 부작용은 함수가 작업을 수행하는 데 필수적일 수 있지만, 부작용이 예상치 못한 결과를 초래할 수도 있다. 이런 경우 디버깅이 어렵기 때문에 함수를 작성할 때 가능한 부작용을 최소화하고 부작용이 발생하는 경우에도 그 영향을 최소화해야 한다.
def add_item_to_list(item, lst=[]):
lst.append(item)
return lst
>>> add_item_to_list(1)
[1]
>>> add_item_to_list(2)
[1, 2]
>>> add_item_to_list(3)
[1, 2, 3]
5.함수의 길이: 함수의 길이는 함수를 작성할 때 고려해야 할 중요한 요소입니다. 함수가 너무 길면 코드의 가독성이 저하되며, 함수의 목적을 파악하는 데 어려움이 생깁니다. 함수를 작성할 때는 함수의 길이를 최소화하고, 함수가 하나의 작업만 수행하도록 만들어야 합니다.
6.테스트하기 좋은 함수: 테스트하기 좋은 함수는 입력값에 대한 예상한 결과를 반환하며, 부작용이 없는 함수이다. 이러한 함수는 단위 테스트(Unit Test)를 수행하기에 매우 적합합니다.
def sum(a, b):
return a + b
def test_sum():
assert sum(1, 2) == 3
assert sum(0, 0) == 0
assert sum(-1, 1) == 0
assert sum(100, -100) == 0
짧지만 제가 생각하는 좋은 코드와 그리고 함수에 대해서 정리해봤습니다. 저도 주기적으로 글을 읽고 그리고 코드를 점검하면서 위의 부분들을 잘 지켜나가려고 노력보려고합니다!
다들 좋은 주말 되세요!🫡
'Computer Engineering > Design' 카테고리의 다른 글
간단한 원칙으로 좋은 HTTP API 만들기 (0) | 2024.02.18 |
---|---|
의존성 역전 원칙(DIP) 예제 코드 및 설명 (2) | 2023.08.15 |