SMAIVNN
article thumbnail

어느덧 2주차 진행되는 스터디입니다. 이번 스터디의 주제는 이상적인 함수 작성하기입니다.

우리 스터디는 매주 한명씩 돌아가면서 스터디를 리드하기로 하였는데요. 이번주는 제가 하게 되었습니다..

뛰어나신 현업자분들 사이에서 준비해보려고 하니 생각보다 부담이 크더라구요. 

https://images.app.goo.gl/epAnhduKs3nmKVti6

 

그럼 스터디 내용을 시작하기에 앞서 책을 읽고난 후의 느낀점 정리를 먼저 해보겠습니다.

 

스터디 전 책 읽기

함수를 무조건 작게!

책의 저자는 함수를 무조건 작게 만들라고 합니다. 한 화면을 넘어가지 않도록, 그리고 if/else 등의 블록은 한 줄이면 충분하다. 들여쓰기는 1단 2단이면 충분하다..등 많은 의견을 추가하지만 결국 작고 가독성 좋은 함수가 좋다! 라는 의견입니다.

 

사실 저 의견들은 가독성의 측면에서 정말 좋은 의견이라도 생각합니다. 

코드가 길어지고 서로 다른 추상화 수준을 갖는 함수들이 존재할 때 이 코드를 보는 다른 사람들은 이해하기가 정말 힘듭니다. 설령 그나마 짧은 코드라도 서로 다른 수준 추상화 수준을 갖는다면 가독성이 좋은 코드라고 할 수 있을까요??

 

더보기

추상화 수준이란?

추상화 수준은 코드에서 "얼마나 구체적인지" 또는 "얼마나 일반적인지"를 나타내는 개념입니다. 높은 추상화 수준의 코드는 시스템의 전반적인 동작이나 큰 개념을 다루며, 낮은 추상화 수준의 코드는 세부적인 구현이나 구체적인 작업을 다룹니다.

 

예를 들어:

  • 높은 추상화 수준: 전체 프로세스를 나타내는 함수
  • 중간 추상화 수준: 특정 작업을 수행하는 함수
  • 낮은 추상화 수준: 세부적인 구현을 다루는 코드 

 

책에서는 가독성 좋은 코드를 글 처럼 위에서 아래로 읽히는 코드라고 말합니다. 즉, 가능하면 같은 추상화 수준에서 술술 읽히는 코드라는 것이죠.

 

다른 사람들은 어떻게 생각 할까요?

[네이버클라우드 개발자 스토리]에서도 고민한 내용이 있어 글을 공유해봅니다.

 

이들은 가독성에는 표현적 가독성(눈에 잘 들어오는 코드, 읽기 편한 코드), 기능적 가독성(직관적으로 파악할 수 있는 코드)이 존재한다고 합니다.

 

저 또한 마찬가지라고 생각합니다. 사이드 프로젝트를 몇 개 완료한 지금, 이전에 작성한 코드를 보면서 드는 생각은 내가 너무 생각 없이 가독성 좋지 않는 코드를 작성해왔구나입니다.

 

특히 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지 만을 해야 한다. (SRP) 라는 이 의견을 잘 지키지 못하고 있었습니다. 그러다보니 난잡해지고, 다시 돌아봤을 때 이해하기도 어려웠습니다. 만약 같이 협업하는 다른 사람이였다면 차라리 주석을 달아주세요.. 설명해주세요라며 언제든 전화해도 이상하지 않았다고 느껴집니다.

 

물론 스터디원분들에게 여러분은 이를 잘 지키고 있으신가 여쭤봤는데, 그 분들도 여러가지 사정에 의해 지키지 못할때가 더 많다고 해주신 것이 나름의 위로가 되었습니다. (휴 나만 그런게 아니구나!)

 

결국 부수효과를 일으키지 않는 코드. 한 가지 일 안에서 다른 작업 없이 오롯한 기능을 하는 함수! 

이것이 이번 파트에서 책을 읽고 정리한 내용입니다.

 

스터디 시작

스터디는 아래와 같이 목차를 진행했습니다. 아무래도 책이 책이다보니 실제 사례 혹은 직접 구현해보기가 더 도움이 많이 되더라구요. 

 

우선, 스터디원분들과 함께 함수 하면 떠오르는 것을 브래인스토밍 하며 마인드맵을 그려봤습니다. 아래 그림 한 장으로 책의 내용 요약, 그리고 고민할 부분 등 다양하게 나타나는 것 같아서 좋았습니다. 

 

그리고 이번에는 3번 항목의 코드 줄이기입니다.

코드는 아래와 같습니다. 스터디를 위해 일부러 지독하게 만들어버린 코드입니다. ㅋㅋ (스터디원분도 보고 진짜 냄새나네요라고 한..) 

def process_data(data_list):
    import datetime
    result = []
    for data in data_list:
        if 'date' in data:
            date_str = data['date']
            date_obj = datetime.datetime.strptime(date_str, '%Y-%m-%d')
            if date_obj.weekday() < 5:
                data['is_weekday'] = True
            else:
                data['is_weekday'] = False
        else:
            data['is_weekday'] = None

        if 'value' in data:
            value = data['value']
            if isinstance(value, str):
                if value.isdigit():
                    data['value'] = int(value)
                else:
                    try:
                        data['value'] = float(value)
                    except ValueError:
                        data['value'] = 0
        else:
            data['value'] = 0

        result.append(data)
    return result

 

분석 및 개선 되는 코드는 아래 접은 글로 첨부하겠습니다. 여러분도 해보세요.

더보기

분석 포인트:

  1. 함수가 너무 크고 복잡하다
    • 여러 가지 작업을 한 함수에서 모두 처리하고 있음.
  2. 단일 책임 원칙 위반
    • 날짜 처리와 값 변환 등 서로 다른 책임을 한 함수에서 수행.
  3. 추상화 수준의 혼재
    • 높은 수준의 로직과 낮은 수준의 세부 구현이 섞여 있음.
  4. 의미 없는 변수 이름
    • data, result 등 추상적인 이름 사용.
  5. 매직 넘버 사용
    • 요일 계산 시 숫자 1과 5를 직접 사용.

잘 적용된 원칙

  • 명시적인 조건 검사
    • 데이터의 존재 여부를 확인하고 처리.
  • 예외 처리 시도
    • try-except 블록을 사용하여 오류를 방지하려는 노력 (Python 코드에서).

개선 방안

  1. 함수 분리
    • 날짜 처리를 위한 함수, 값 변환을 위한 함수로 분리.
  2. 의미 있는 함수 및 변수 이름 사용
    • process_date, convert_value 등의 함수 이름 사용.
  3. 추상화 수준 일관성 유지
    • 한 함수 내에서는 동일한 추상화 수준의 작업만 수행.
  4. 상수 사용으로 매직 넘버 제거
    • 요일을 나타내는 숫자를 상수로 선언하여 가독성 향상.
  5. 에러 처리 개선
    • 예외 상황에 대한 명확한 처리 및 로그 추가.

아래는 크나마 개선 된 코드입니다!

from datetime import datetime
from typing import Any, Dict, List, Optional

WEEKDAY_CUTOFF = 5
DATE_FORMAT = '%Y-%m-%d'

def process_data(data_list: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    return [process_single_data(data) for data in data_list]

def process_single_data(data: Dict[str, Any]) -> Dict[str, Any]:
    new_data = data.copy()
    new_data['is_weekday'] = determine_weekday(new_data.get('date'))
    new_data['value'] = convert_value(new_data.get('value'))
    return new_data

def determine_weekday(date_str: Optional[str]) -> Optional[bool]:
    if not date_str:
        return None
    try:
        date_obj = datetime.strptime(date_str, DATE_FORMAT)
        return date_obj.weekday() < WEEKDAY_CUTOFF
    except ValueError:
        # 날짜 형식이 잘못된 경우 None 반환
        return None

def convert_value(value: Any) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0

 

이후에는 한 주제를 갖고 제공 된 스켈레톤 코드에서 시작해서 제작해보는 과정을 거쳤습니다.

제가 준비해온 문제가 조금 적합하지 않은 것 같아서 눈앞에서 스터디 리더 분이 라이브코딩을 해주셨습니다..갓갓능력자..감사합니다하하

 

아쉽게도 해당 문제는 비공개 면접에서 사용하는 코드를 활용하였기 때문에 공개하기에는 조금 무리가 있습니다!

다만 코딩테스트 준비하는 기분도 들고 재밌었습니다. 이것 처럼 공부를 위한 알고리즘은 참 재밌는데.. 코테는 정말 재미없습니다. (기습 코테 비난)

마무리

이번 시간이 끝난 저의 (말로만)한줄 회고인데요. 

 

- 저번과 다른 색다른 방식으로 진행해보는 것도 좋았다.

- 다른 사람의 방식과도 비교해보며 많이 배웠다! 다음 스터디장 기대된다!

- 직접 스터디 목차를 준비해보니 생각보다 굉장히 어려웠다.

 

추가로 다른 스터디원분들의 회고입니다.

 

- 사람마다 문제를 푸는 방식이 다르다는 것에서 재미있던것 같다.

- **님 머리속에 들어가서 살고싶다.

- 클린 코드 관점의 함수에 대해 논의해보고, 개선점을 찾아보고, 다 같이 문제 풀어보는 것이 즐거웠고, 기승전결이 완벽한 스터디 아닐까 싶다.

- 주어진 시간 안에 클린코드와 알고리즘을 생각하면서 짜보려고 하니 둘 다 놓친 것 같다..

- 함수를 가지고도 많은 클린코드를 적용할 만한 것들이 많았다.

- 좀더 빠르게 구현 가능한 문제를 가져올걸!

- 성민님의 스터디 리딩 방식도 너무 유익했음. 재미있는 예시들을 GPT 로 가져와서 좋았씁니다

 

한번 직접 리드해보는 경험도 무척 귀중한 것이라고 생각합니다. 많은 도움이 됐고 확실히 하면서 배우는게 있습니다.

 

그리고, 이 책을 읽기 전 유튜브를 보다가 우연히 알고리즘을 통해 본 영상이 하나 있습니다.

 

https://youtu.be/th7n1rmlO4I?si=5aAjLgHVtuFlEM-M

YouTube 코딩애플

 

요약하자면 클린코드에 대한 여러가지 공감과 비판을 하는 영상인데요!

 

저번 글에서도 말했고, 너무 당연한 말이기도 하고, 영상에서도 말하듯이 클린 코드는 맹신할 필요 없다고 생각합니다.

 

결국 사람이 코드를 작성하는 것인 만큼, 다른 사람이 이해하기 쉽고 협업하기 좋은 코드가 클린 코드라고 생각합니다. 그러기 위해서는 컨벤션을 정하고 그라운드 룰을 정하는게 꽤나 중요하겠지요..

 

또, 해당 유튜브 영상의 댓글중 가독성의 측면을 생각해볼 내용이 있어 가져왔는데요.

 

"함수가 많다고 왔다갔다 하는게 아니다. 함수 이름을 읽고 그런 일을 하는 구나하고 자세히 분석하지 않고 그냥 넘어가는 것이다. 잘 짜져 있는 코드라면, 그렇게 하면 된다."

 

결국 우리가 계속 말하는 네이밍의 중요성, 그리고 추상화 단계의 중요성을 이해하고 말하는 것 같습니다. 이 다음 스터디는 오늘 할 주석 부분인데, 이 부분도 기대가 됩니다! 

 

 

profile

SMAIVNN

@SMAIVNN

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!