반응형

 

클린 코드의 의미


  • 기계나 스크립트가 판단할 수 없고 전문가가 판단할 수 있는 것
  • 프로그래밍 언어란 아이디어를 다른 개발에게 전달하는 것이고 여기에 클린 코드의 진정한 본질이 있다

⇒ 클린 코드를 정의하기 보다는 좋은 코드 나쁜 코드의 차이점을 확인하고 훌륭한 코드와 좋은 아키텍쳐를 식별하여 자신만의 정의를 하는 것이 좋음

클린코드의 중요성


  1. 민첩한 개발과 지속적인 배포가 가능
  • 유지보수가 가능한 상태로 가독성이 높아야 기획자가 새롭게 기능을 요구할 때마다 리팩토링을 하고 기술 부채를 해결하느라 시간이 오래 걸리지 않음
  1. 기술부채 발생
  • 잠재적인 문제로 언젠가는 돌발 변수가 될 수 있음

클린 코드에서 코드 포매팅의 역할


PEP (Python Enhancement Proposal)

  • PEP 8: Style Guide for Python Code 로 가장 잘 알려진 표준이며 띄어쓰기, 네이밍 컨벤션, 줄 길이 제한 등의 가이드라인을 제공

클린 코드를 위한 보조적인 역할, PEP8을 100% 준수한다고 하더라도 여전히 클린 코드의 요건을 충족하지 못할 수 있음

PEP8 특징

  1. 검색 효율성
  • PEP8: 변수에 값을 할당하는 경우와 함수의 키워드 파라미터에 값을 할당하는 경우를 구분
# core.py
#
#
#

def get_location(location: str = ""):
    pass

#변수에 값을 할당할 때 띄어쓰기 사용 O
current_location = get_location() 

#키워드 인자에 값을 할당할 때 띄어쓰기사용 X
get_location(location=current_location) 

  • location이라는 키워드 인자에 값이 할당되는 경우를 찾는 경우

$ grep -nr “location=” ./core.py:13:get_location(location=current_location)

  • nr 옵션
    • n : line number 표시
    • r : 해당 디렉토리에서 recursive 하게 subdirectory도 검색
  • location이라는 변수에 값이 할당되는 경우를 찾는 경우

$ grep -nr “location =” ./core.py:10:current_location = get_location()

  1. 일관성
  2. 더 나은 오류 처리
  3. 코드 품질

문서화(Documentation)


코드주석(Code comments)

  • 가능한 한 적은 주석을 갖는 것을 목표로 해야 함
    • 주석 처리된 코드는 절대 없어야 함

Docstring

  • 소스 코드에 포함된 문서 (리터럴 문자열)
  • 내가 작성한 컴포넌트를 다른 엔지니어가 사용하려고 할 때 docstring을 보고 동작방식과 입출력 정보등을 확인 할 수 있어야 함
  • Python은 동적인 데이터 타입을 갖기 때문에 docstring이 큰 도움이 됨

docstring은 코드에서 분리되거나 독립된 것이 아니라 일부

단점은 지속적으로 수작업을 해야 한다는 것 (코드가 변경되면 업데이트를 해야함)

⇒ 가치 있는 문서를 만들기 위해 모든 팀원이 문서화에 노력해야 함

어노테이션

  • PEP-3107에서 어노테이션을 소개
    • 코드 사용자에게 함수 인자로 어떤 값이 와야 하는지 힌트를 주자는 것
from dataclasses import dataclass

@dataclass
class Point:
    lat: float
    long: float

def locate(latitude: float, longitude: float) -> Point:
    """맵에서 좌표에 해당하는 객체를 검색"""
    pass

  • 함수 사용자에게 힌트를 주지만 파이썬이 타입을 검사하거나 강제하지는 않음

어노테이션으로 타입만 지정할 수 있는 것은 아니고, 인터프리터에서 유효한 어떤 것(변수의 의도를 설명하는 문자열, 콜백이나 유효성 검사 함수로 사용할 수 있는 callable)도 가능

EX) 몇 초 후에 어떤 작업을 실행하는 함수

def launch_task(delay_in_seconds):
    pass
  • delay_in_seconds 파라미터는 긴 이름을 가지고 있어 많은 정보를 담고 있는 것 같아 보이지만 사실 충분한 정보를 제공하지 못함
    • 허용 가능한 지연시간은 몇초?
    • 분수를 입력해도 되나?
Seconds = float
def launch_task(delay: Seconds):
    pass
  • Seconds 어노테이션을 사용하여 시간을 어떻게 해석할지에 대해 작은 추상화 진행
  • 나중에 입력 값의 형태를 변경하기로 했다면 이제 한 곳에서만 관련 내용을 변경하면 됨
    • Seconds = float

어노테이션을 사용하면 __annotations__ 이라는 특수한 속성이 생김

  • 어노테이션의 이름과 값을 매핑한 dictionary

아래 locate함수에 대해 __annotations__ 을 출력해보면,

from dataclasses import dataclass

@dataclass
class Point:
    lat: float
    long: float

def locate(latitude: float, longitude: float) -> Point:
    """맵에서 좌표에 해당하는 객체를 검색"""
    pass

print(locate.__annotations__)

{'latitude': <class 'float'>, 'longitude': <class 'float'>, 'return': <class 'main.Point'>}

타입 힌트는 단순히 데이터 타입을 확인하는 것이 아니라 유의미한 이름을 사용하거나 적절한 데이터 타입 추상화를 하도록 도와줄 수 있음

def process_clients(clients: list):

def process_clients(clients: list[tuple[int, str]]):

...

from typing import Tuple
Client = Tuple[int, str]
def process_clients(clients: list[Client]):

어노테이션을 도입하면 클래스를 보다 간결하게 작성하고 작은 컨테이너 객체를 쉽게 정의 가능

  • @dataclass 데코레이터를 사용하면 별도의 __init__ 메소드에서 변수를 선언하고 할당하는 작업을 하지 않아도 바로 인스턴스 속성으로 인식
# Before
class Point:
    def __init__(self, lat, long):
        self.lat = lat
        self.long = long
# After
from dataclasses import dataclass
@dataclass
class Point:
    lat: float
    long: float
# After
from dataclasses import dataclass

@dataclass
class Point:
    lat: float
    long: float

print(Point.__annotations__) 
# {'lat': <class 'float'>, 'long': <class 'float'>}
print(Point(1, 2))
# Point(lat=1, long=2)

Q. 어노테이션은 docstring을 대체하는 것일까?

  • 둘은 상호보완적인 개념
def data_from_response(response: dict) -> dict:
    if response["status"] != 200:
        raise ValueError
    return {"data": response["payload"]}
  • input, output 형식에 대해서는 알 수 있지만 상세한 내용은 알 수 없음
    • ex) respone 객체의 올바른 instance 형태

상세한 내용에 대해서 docstring으로 보완할 수 있음

def data_from_response(response: dict) -> dict:
    """response의 HTTP status가 200이라면 response의 payload를 반환
    
    
    - response의 예제::
    {
        "status": 200, # <int>
        "timestamp": "....", # 현재 시간의 ISO 포맷 문자열
        "payload": {...} # 반환하려는 dictionary 데이터
    }
    
    
    """
    if response["status"] != 200:
        raise ValueError
    return {"data": response["payload"]}
  • input, output의 예상 형태를 더 잘 이해할 수 있고 단위 테스트에서도 유용한 정보로 사용됨

도구설정

  • 반복적인 확인 작업을 줄이기 위해 코드 검사를 자동으로 실행하는 기본도구설정

데이터 타입 일관성 검사

  • mypy, pytype 등의 도구를 CI build에 포함시킬 수 있음
$ pip install mypy
from typing import Iterable
import logging

logger = logging.getLogger()

def broadcast_notification(message: str, relevant_user_emails: Iterable[str]):
    for email in relevant_user_emails:
        logger.warning(f"{message} 메세지를 {email}에게 전달")

broadcast_notification("welcome", "user1@domain.com")
# mypy가 오류 내뱉지 않음
$ mypy core.py  
Success: no issues found in 1 source file

welcome 메세지를 u에게 전달
welcome 메세지를 s에게 전달
welcome 메세지를 e에게 전달
welcome 메세지를 r에게 전달
welcome 메세지를 1에게 전달
welcome 메세지를 @에게 전달
welcome 메세지를 d에게 전달
welcome 메세지를 o에게 전달
welcome 메세지를 m에게 전달
welcome 메세지를 a에게 전달
welcome 메세지를 i에게 전달
welcome 메세지를 n에게 전달
welcome 메세지를 .에게 전달
welcome 메세지를 c에게 전달
welcome 메세지를 o에게 전달
welcome 메세지를 m에게 전달

  • 잘못된 호출. 문자열 또한 iterable 객체이므로 for 문이 정상 동작하지만 유효한 이메일 형식이 아님

리스트나 튜플만 허용하도록 더 강력한 타입 제한을 주면,

from typing import List, Tuple, Union
import logging

logger = logging.getLogger()

def broadcast_notification(
    message: str, relevant_user_emails: Union[List[str], Tuple[str]]
):
    for email in relevant_user_emails:
        logger.warning(f"{message} 메세지를 {email}에게 전달")

broadcast_notification("welcome", "user1@domain.com")
> mypy core.py
core.py:14: error: Argument 2 to "broadcast_notification" has incompatible type "str"; expected "list[str] | tuple[str]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

일반적인 코드 검증

  • 데이터 타입 이외에도 일반적인 유형의 품질 검사도 가능
  • pycodestyle(pep8), flake8
  • 더 엄격한 pylint

자동 포매팅

  • black formatter
  • PEP-8보다 엄격하게 포매팅하여 문제의 핵심에 보다 집중
  • —check 옵션을 사용해 코드를 포맷하지않고 표준을 준수하는지 검사만 하는 것도 가능
    • CI 프로세스에 통합하여 유용하게 사용될 수 있음

자동 검사 설정

  • 리눅스 개발환경에서 빌드를 자동화하는 가장 일반적인 방법은 Makefile을 사용하는 것
 
반응형
반응형

Github Actions란?

Github에서 제공하는 CI/CD 등을 위한 서비스로 자동으로 코드 저장소에서 어떤 Event가 발생했을 때 작업(Workflow)이 일어나게 하거나 주기적으로 반복해서 실행시킬 수 있도록 도와주는 서비스

다양한 예시

  • 레포지토리에 코드가 푸쉬되었을 때 자동으로 Test Code 검사
  • 깃헙 레포에서 다양한 사람들과 협업할 때 coding convention (ex) code formatting)을 잘 맞췄는지 체크
  • Pull request가 merge되었을 때 docker image를 빌드하고 자동으로 Amazon Elastic Container Registry(ECR) 에 올리기

본 글에서는 Github actions를 통해 repository에 pushpull_request가 발생할 때마다 Python 코드의 formatting을 체크하는 black을 실행시켜 주기적으로 검사하는 작업을 위한 yaml 파일을 설명

예시를 통해서 Github Actions가 어떻게 작동하는지 알아봄

Black formatter yml파일 예시

Github actions는 yaml파일을 통해 서비스를 실행시킬 수 있다

main branch (default branch)에 .github/workflows 폴더 추가 후 아래 내용을 yaml 파일에 추가 (ex) black.yml)

name: black formatter # github action 이름

on:
  push: # 모든 branch에 commit이 push 될때 아래 workflow(jobs) 실행
    branches:
      - '*'
  pull_request:# 모든 branch에 pull_request 요청이 왔을 때 workflow(jobs) 실행
    branches:
      - '*'

jobs:
  Black_code_formatter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: psf/black@stable
        with:
          options: "-l 79 --diff --check"
          src: "./"
 

 

위의 yaml 파일 내용 설명

1) name

- name: black formatter
  • Github actions에서 workflow의 이름
    • workflow: 하나 이상의 job을 실행시키는 자동화된 process. 여러개의 job을 묶은거라 생각하면 되고 job은 아래에 다시 설명

왼쪽 빨간 박스처럼 이름이 표시됨

2) on push

- name: black formatter

- on:
    push: # commit을 push 했을 때 github action이 돌게한다

  • ‘어떤 event가 발생했을 때’ 에 해당
  • 가능한 event 종류들
    • push, pull_request, create 등등 (링크)

 

3) branches

name: black formatter # github action 이름

on:
  push:
    branches:
      - '*'

 

  • “어떤 branch”에 코드가 push 된 경우에 workflow를 실행할지 결정
  • 위의 예는 모든 branch( * 로 표시)에 push되는 경우이고, 아래처럼 특정 branch에 push될 때를 지정할 수 있음
on:
  push:
    branches:
      - 'develop'

 

4) on pull_request branches

name: black formatter # github action 이름

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - '*'
  • 위는 모든 branch에 pull_request가 열렸을 때 workflow를 실행한다는 의미
  • 각 event (ex) pull_request) 마다 types(ex) opened review_requested, closed) 이 있음
  • 아래와 같이 types를 지정해줄 수 있음 (pull_request는 default type이 opened)
on:
  pull_request:
    types: [opened, reopned]

 

5) jobs & each job name

  • workflow는 하나 이상의 job 들로 구성되는데 jobs에 대해 설정

기본형태

jobs:
  <job_id_1>: # job1의 이름 (github UI에 표시됨)

  <job_id_2>: # job2의 이름

 

name: black formatter # github action 이름

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  Black_code_formatter:

 

6) runs-on

  • 어떤 형태의 os 에서 job이 실행될지를 정의 (기본적으로 github이 제공하는 virtual machine)
    • window, ubuntu, macOS
name: black formatter # github action 이름

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  Black_code_formatter:
    runs-on: ubuntu-latest

  • ubuntu-lateast 를 image로 사용

 

7) steps

  • 각각의 job은 steps 라고 하는 연속된 task들로 구성됨
  • command를 실행할 수 있고, setup task 도 실행할 수 있음
  • 예시
steps:
  run: echo 'Hello'

name: black formatter

on:
  push: #
    branches:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  Black_code_formatter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: psf/black@stable
        with:
          options: "-l 79 --diff --check"
          src: "./"

 

8) uses

  • job에서 steps의 일부로서 action을 선택하는 명령어
  • 아래 예는 public action을 사용
name: black formatter

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  Black_code_formatter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: psf/black@stable
        with:
          options: "-l 79 --diff --check"
          src: "./"

public action을 사용하는 예

 

9) with

  • uses 키워드 옆의 각각의 action들에 대해 정의된 input parameter map 을 아래에 입력하게하는 키워드
name: black formatter

on:
  push: 
    branches:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  Black_code_formatter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: psf/black@stable
        with:
          options: "-l 79 --diff --check"
          src: "./"

  • black formatter는 options 키워드와 src 키워드를 이용해 argument를 넘겨줄 수 있음 (링크)

options

  • l 79 : 한줄에 혀용되는 character 수로 79자로 설정 (default 88)
  • —diff : 파일을 수정하진 않고 수정되어야 할 부분을 로그로 알려주고 수정될 형태를 제안
  • —check : 파일을 수정하진 않고 status code를 반환
    • status code 0: formatting 되어야 할 부분 없음
    • status code 1: formatting 되어야 할 부분 있음
    • status code 123: code error (internal error)

src

  • “./” : 전체 코드에 대해서 formatting 확인

위의 yaml 파일을 repo에 추가하면 아래와 같이 Workflow가 생성되어 작동되는걸 확인할 수 있음

체크표시는 성공적으로 완료된 상태

해당 워크플로우를 눌러보면 우리가 위에서 작성했던 Job의 이름이 표시되고 job을 누르면 구체적으로 어떤 step들이 실행되었는지 표시된다

반응형
반응형

Visual Studio Code에서 Windows Subsystem for Linux와 연동하여 C/C++ 코드를 작성할 때, ctrl + p를 눌러서 제안되는 항목들을 보려했지만, 아래 사진처럼 No suggestions이라고 발생합니다. 

No suggestion

해결책

- Ubuntu package들이 update되지 않아 발생하는 문제로 command prompt에 아래 명령어를 입력하여 package를 update

sudo apt-get update

- command prompt에 아래 명령어를 입력하여 GNU compiler tools and the GDB debugger 설치

sudo apt-get install build-essential gdb

- 이후 VS code를 재시작하면 아래처럼 suggestion list를 볼 수 있음

Suggestion !!

 

참고

- Using C++ and WSL in VS Code (https://code.visualstudio.com/docs/cpp/config-wsl)

반응형

+ Recent posts