Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[클린코드 파이썬] 5장: 코드 악취 감지와 대응 본문

Python/클린 코드, 이제는 파이썬이다

[클린코드 파이썬] 5장: 코드 악취 감지와 대응

끈기JK 2024. 9. 1. 13:56

중복된 코드

중복된 코드란 프로그램에 다른 코드를 복사해서 붙여넣는 방식으로 만들 수 있는 모든 소스 코드를 말한다.

중복된 코드가 문제시 되는 이유는 코드 변경이 까다로워지기 때문이다. 즉 중복된 코드의 복사본 하나에 변경을 가하면 프로그램 내의 모든 복사본도 함께 변경해야 한다.

중복된 코드를 해결하는 방법은 중복 자체를 없애는 것이다. 즉 코드를 함수나 루프문 안에 배치해 프로그램 내에서 한 번만 나타나게 해야 한다.

def askFeeling():
	print('How are you feeling?')
    feeling = input()
    print('I am happy to hear that you are feeling ' + feeilng + '.')

print('Good morning!')
askFeeling()
print('Good afternoon!')
askFeeling()
print('Good evening!')
askFeeling()

 

 

 

매직 넘버

주석 한 줄을 달아 숫자의 의미를 명확히 할 수도 있다.

expiration = time.time() + 604800  # 1주 뒤에 만료

더 좋은 해결책은 이 매직 넘버를 상수로 대체하는 것이다. 상수는 처음 할당한 뒤에 값이 변하지 않아야 하는 변수로서, 일반적으로 대문자로 표기해서 구분한다. 일반적으로 상수는 소스 코드 파일 상단에 전역 변수로 정의한다.

# 여러 시간 값에 대한 상수 설정
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE
SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR
SECONDS_PER_WEEK = 7 * SECONDS_PER_DAY

# 생략

expiration = time.time() + SECONDS_PER_WEEK  # 1주 뒤에 만료

 

동일한 값의 매직 넘버라도 다른 용도로 쓸 수 있다면 별도의 상수를 사용해야 한다.

NUM_CARDS_IN_DECK = 52
NUM_WEEKS_IN_YEAR = 52

 

매직 넘버라는 용어는 숫자가 아닌 값에도 적용될 수 있다.

# 각 방향에 대한 상수 설정
NORTH = 'north'
SOUTH = 'south'
EAST = 'east'
WEST = 'west'

while True:
	print('Set solar panel direction:')
    direction = input().lower()
    if direction in (NORTH, SOUTH, EAST, WEST):
    	break

print('Solar panel heading set to:', direction)
if direction == NRTH:
	print('Warning: Facing north is inefficient for this panel.')

NRTH 오타로 NameError 예외가 발생해, 프로그램을 실행하면 버그가 명확하게 보인다.

 

 

 

주석 처리된 코드와 죽은 코드

일시적으로 코드를 주석 처리해서 실행하지 않게 하는 방법은 테스트 과정에서 흔히 사용된다.

doSomethis()
#doAnotherThing()
doSomeImportantTask()
doAnotherThing()

 

죽은 코드란 도달할 수 없거나 논리적으로 결코 실행할 수 없는 코드다.

import random
def coinFlip():
    if random.randint(0, 1):
    	return 'Heads!'
    else:
    	return 'Tails!'
    return 'The coin landed on its edge!'
print(coinFlip())
#Tails!

코드 실행이 return 'The coin landed on its edge!' 행에 도달하기 전에 if와 else 블록의 코드에서 반환되기 때문에 해당 행은 죽은 코드다.

 

스텁stub은 이러한 코드 악취 판단 규칙의 예외다. 스텁은 아직 구현되지 않은 함수나 클래스처럼 향후 코드가 작성될 위치를 나타내는 플레이스홀더다.

def exampleFunction():
    pass

이 함수는 호출되어도 아무것도 하지 않지만, 코드가 추가될 것임을 시사하는 기능을 한다.

 

아니면, 실수로 미구현 함수를 호출하는 상황을 방지하기 위해 raise NotImplementedError 문으로 스텁을 걸 수 있다.

def exampleFunction():
    raise NotImplementedError

 

 

 

디버깅 출력

로그 파일은 프로그램에서 대량을 정보를 기록할 수 있으므로 이전에 실행된 것과 비교가 가능하다. 파이썬에 내장된 logging 모듈은 다음과 같이 단 세 줄의 코드만 사용해 로그 파일을 손쉽게 만드는 데 필요한 기능을 제공한다.

import logging
logging.basicConfig(filename='log_filename.txt', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug('This is a log message.')

 

print()를 사용해 화면에 표시하는 대신 logging.debug()를 호출해 텍스트 파일에 정보를 쓸 수 있다.

 

 

 

숫자 접미사가 붙은 변수

오타 방지를 위해 사용자에게 암호를 두 번 입력하도록 요청하는 등록 폼의 경우, 암호 문자열을 password1, password2라는 변수에 저장할 수 있다. 이보다 password와 confirm_password가 더 나은 변수 이름일 것이다.

 

다른 예로 출발지 좌표와 목적지 좌표를 다루는 함수가 있다고 할 때 x1, y1, x2, y2 라는 파라미터보다 start_x, start_y, end_x, end_y 가 더 낫다.

 

숫자 접미사가 3개 이상인 경우 리스트를 사용하거나 집합 데이터 구조를 사용해 데이터를 컬렉션으로 저장할 수 있다. 예를 들어 pet1Name, pet2Name, pet3Name 등의 값을 petNames라는 리스트 하나에 저장할 수 있다.

 

 

 

코드 악취에 관한 잘못된 통념

 

함수 마지막에는 return 문이 하나만 있어야 한다?

'하나의 입구, 하나의 출구' 아이디어는 어셈블리어와 포트란 언어로 프로그래밍하던 시절에 나온 조언을 잘못 해석한 데서 비롯된 것이다. 이러한 언어들은 서브루틴(함수와 비슷한 개념)의 어떤 위치에도(중간을 비롯해) 진입이 가능하고, 서브루틴 내부에서 어떤 부분이 실행되었는지 디버깅을 하기 어렵게 되어 있다. 함수에는 이런 문제가 없다(함수를 호출하면 함수를 구성하는 코드의 처음부터 실행된다).

함수나 메소드마다 return 문으르 하나씩만 유지하려면, 여러 return 문을 유지하는 경우와 비교해 일련의 난해한 if-else 문이 필요하므로 훨씬 더 혼란스럽다. 함수나 메소드에 return 문이 둘 이상 있어도 괜찮다.

 

 

플래그 인수는 나쁘다?

함수 또는 메소드 호출의 부울boolean 인수를 플래그 인수flag argument라고 부르기도 한다. 프로그래밍에서 플래그는 '가능함' 또는 '불가능함'과 같은 이진 설정을 나타내는 값으로서 흔히 부울 값으로 나타낸다.

def someFunction(flagArgument):
    if flagArgument:
    	# 특정 모드 실행...
    else:
    	# 완전히 다른 특정 코드 실행...

실제로 함수가 이와 같을 경우, 인수를 통해 함수의 코드 절반을 실행할지 말지 결정하기보다는 두 개의 함수를 별도로 만들어야 한다. 하지만 반대로 sorted() 함수의 reverse 키워드 인수에 대한 부울 값을 전달해 정렬 순서를 결정할 수 있다. 이 코드를 sorted()와 reverseSorted()라는 이름의 두 함수로 분할해도 개선되는 것은 없다.

 

 

전역 변수는 나쁘다?

함수와 메소드는 프로그램 내의 작은 프로그램과 같아서, 코드를 포함함은 물론이고, 함수가 반환될 때 잊힐 지역 변수도 포함된다. 이는 프로그램이 종료된 후 변수가 잊히는 방식과 비슷하다.

그러나 전역 변수를 사용하는 함수와 메소드는 이렇게나 유용한 격리 기능을 일부 잃는다. 함수에서 사용하는 모든 전역 변수는 사실상 인수와 마찬가지로 함수에 대한 또 다른 입력으로 사용된다. 인수가 많아지면 복잡도도 높아지며 그만큼 버그 발생 가능성 또한 높아진다. 전역 변수의 값이 잘못되어 함수에 버그가 나타났다면, 해당 에러 값이 설정된 위치를 프로그램 전체에서 다 찾아봐야 한다.

 

전역 상수는 잘못된 프로그래밍 관행으로 간주되지 않는다는 점을 명심하자. 그 값은 결코 변하지 않기 때문에, 전역 상수는 전역 변수와는 달리 코드에 복잡성을 추가하지 안흔ㄴ다. 프로그래머들이 "전역 변수는 나쁘다."고 언급할 때, 상수 값은 해당되지 않는다.

 

 

주석은 불필요하다?

실제로, 나쁜 주석이 달린 코드는 주석이 전혀 없는 코드보다 더 나쁘다.

주석은 이해하기 쉬운 언어로 작성되어 변수, 함수, 클래스 이름으로는 전달하지 못하는 정보를 전달할 수 있다.

그러나 일반적으로는, 프로그램에 주석이 너무 많거나 오도될 주석이 있는 경우보다 아예 없거나 부족한 경우가 대부분이다.