당신이 잘못 알고 있는 프로그래밍 조언들

Heebeom·2023년 3월 5일
1

앞선 포스팅에서 프로그램 오류를 야기하는 코드 악취에 대해 알아보았다. 코드 악취가 나는 프로그램은 프로그래머가 실수할 확률을 높이며, 디버그에 시간을 낭비하게 한다.

이를 방지하기 위해 요즘 프로그래머들은 '클린 코드'에 관심을 가지고, 이에 관련된 조언을 지키려고 노력한다. 하지만 인터넷에선 누구나 정보를 작성할 수 있어, 조언을 무조건적으로 수용해선 안된다. 어떤 조언은 코드를 더욱 더럽게 만든다.

이번 포스팅에서는 유용성이 없어진 잘못된 프로그래밍 조언에 대해 알아보겠다.

본 포스팅은 Al Sweigart의 저서인 『클린 코드, 이제는 파이썬이다』의 일부를 기반으로 작성되었습니다.



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

'하나의 입구, 하나의 출구' 아이디어는 어셈블리어와 포트 시절 이야기이다.

어셈블리어 사용 시절에는 함수 대신 서브루틴(subroutine)을 사용해 '하나의 입구, 하나의 출구' 조언이 유용하였다. 서브루틴은 함수의 어떤 위치에서도 진입이 가능하기 때문에, return문이 여러개 있으면 디버깅하기 불편하였다.

하지만 우리는 최신이고 강력한 파이썬을 사용하고 있다. 함수에서 단 하나의 return문을 사용하려면, 난해한 if-else문이 필요하므로 코드가 더러워진다.

여러개의 return 문은 코드를 깔끔하게 만든다

# return문을 여러개 사용한 예
def get_score(target_hits, is_foul):
	if is_foul is True:
    	return 0
	if target_hit <= 3:
    	return 1
	if target_hit <= 7:
    	return 2
	if target_hit <= 10
    	return 3


# return문을 한개 사용한 예
def get_score(target_hits, is_foul):
	score = 0
    if is_foul is False:
      if target_hit <= 3:
          score = 1
      else if target_hit <= 7:
          score = 2
      if target_hit <= 10
          score = 3
    
    return score

위 코드의 두 예시를 비교해 보자. return문을 한개만 사용할 경우 조건문이 중첩이 되며, 언뜻 봐서는 함수의 알고리즘을 이해할 수 없다.

하지만 반대로 return문을 여러개 사용한 경우는, '파울하면 0을 반환하고, 아니면 맞춘 수에 따라서 1 ~ 3점을 반환한다.'라고 자연스럽게 읽힌다.

이처럼 여러 개의 return문은 코드를 한결 깔끔하게 만든다.

2. 함수에는 try 문이 둘 이상 있으면 안된다?

지나치면 도리어 해가 된다

"함수와 메소드는 한 가지 일을 해야 한다"는 조언은 대부분 맞다. 하지만 예외 처리도 '한 가지 일'로 생각하고 분할한다면, 도리어 코드를 더럽게 만들 것이다.

import os

def delete_with_confirm(file_name):
	try:
    	if (input('삭제할 파일이 ' + file_name + '이(가) 맞습니까? Y/N') == 'Y'):
        	os.unlink(file_name)
    except FileNotFoundError:
    	print('삭제할 파일이 존재하지 않습니다.')

위 예시를 보자. 위 함수는 다음과 같은 기능을 수행한다.

  1. 먼저 파일을 삭제할지 물어보고, 답이 'Y'면 파일을 삭제한다.
  2. 만약 파일이 없으면 에러 메세지를 출력해 예외 처리한다.

예외 처리도 '한 가지 일'로 보고, 위 조언을 따르기 위해 두 로직을 분리해 보자.

import os

def handle_error_delete_with_confirm(file_name):
	try:
    	_delete_with_confirm(file_name)
    except FileNotFoundError:
    	print('삭제할 파일이 존재하지 않습니다.')


def _delete_with_confirm(file_name):
	if (input('삭제할 파일이 ' + file_name + '이(가) 맞습니까? Y/N') == 'Y'):
    	os.unlink(file_name)

위 조언을 따라 (1)예외 처리와 (2)삭제 확인을 따로 분리하였다. 하지만 에외를 처리하는 함수인 handle_error_delete_with_confirm의 이름이 너무 길어져 도리어 코드가 복잡해졌다.

함수는 작고 단순해야 하지만, 그렇다고 항상 '하나의 일'만 하게 제한되어야 한다는 것은 아니다. 우리의 목적은 코드를 깔끔하게 짜는 것이지, 조언을 따르기 위함이 아님을 상기하자.

3. 플래그 인수는 나쁘다?

플래그 인수가 나쁠 때도 있다

함수 또는 메소드 호출의 부울 값을 플래그 인수라도 한다. 아래와 같이 플래그 인수에 따라서 완전히 다른 두 알고리즘을 실행하는 경우는 함수를 분리해야 함이 맞다.

def some_function(flag_argument):
	if flag_argument is True:
    	특정 코드 실행 ...
    else:
    	완전히 다른 코드 실행 ...

하지만 이러한 경우는 극소수이다

하지만 플래그 인수가 있는 함수는 방금 전 사례와 같이 동작하지 않는 경우가 대부분이다. 대부분의 알고리즘은 같지만, 특정한 경우를 위해 플래그 인수를 사용하는 경우다.

파이썬의 내장 함수인 sorted()를 예로 들어 보자. 이 함수는 reverse 인수를 사용해 정렬 순서를 결정할 수 있다. 만약에 sorted()reversed_sorted()라는 함수로 분리한다면, 앞서 말한 '중복된 코드' 악취가 발생할 것이다.

4. 전역 변수는 나쁘다?

전역 '변수'는 나쁘다

전역 변수는 디버깅을 어렵게 한다. 전역 변수의 값이 잘못되어 버그가 발생하면, 해당 값이 잘못된 부분을 프로그램 전체에서 찾아야 한다.

물론 100줄 이하의 프로그램이라면 찾기 어렵지 않다. 하지만 프로그램이 몇천 줄이라면? 몇만 줄이라면? 가상의 프로그램 partyplanner.py 를 살펴보자.

1504.	def calculate_slices_per_guest(num_of_cake_slices):
1505.		global num_of_party_guests
1506.		return num_of_cake_slices / num_of_party_guests
Traceback (most recent call last):
	File "partyplanner.py", line 1898, in <module>
		print(calculate_slices_per_guest(42))
	File "partyplanner.py", line 1506, in calculate_slices_per_guest
		return num_of_cake_slices / num_of_party_guests
    ZeroDivisionError: division by zero

1506줄의 num_of_cake_slices / num_of_party_guests 을 수행할 때, '0으로 나누기 에러'가 발생했다. num_of_party_guests가 0이어야 하는데, 대체 어디서 0이 된걸까? 에러 메세지로는 절대 파악할 수 없다.

전역 '상수'는 나쁘지 않다

전역 '상수'의 경우는 잘못된 프로그래밍 관행이 아님을 명심하자. 값이 절대로 변하지 않기 때문에(프로그램 실행 때는) 전역 변수와는 달리 코드에 복잡성을 추가하지 않는다.

그래서 대부분의 프로그래머들은 상수를 대부분 전역에 놓고 사용한다. 예를 들어 알고리즘 문제를 풀 때, test case의 크기는 상수로 간주하고 전역 변수로 사용하기도 한다.

5. 주석은 불필요하다?

나쁜 주석만 보고 일반화하지 마라

유효하지 않고나 잘못된 정보를 주는 주석은, 다른 프로그래머로 하여금 코드를 오해하게 만들어 없으니만 못하다. 그러나 이와 같이 잠재적인 문제를 일반화해 '모든 주석이 나쁘다'라는 주장을 하는 것은 잘못되었다.

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

그러나 간결하고 유용한 주석은 쓰기 어렵고, 또한 프로그래머가 방금 작성한 코드는 스스로 이해하고 있으므로 "주석은 불필요하다"라고 여길 수 있다.

대부분은 주석이 없거나 부족한 경우가 대부분이다

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

주석은 쓰지 않겠다는 말은 "여객기를 타고 일본에 가면 0.0000001% 확률로 사고가 나니, 수영을 해서 횡단하겠다"는 말과 같다.

효과적인 주석을 작성하는 법은 추후 포스팅에서 다르겠다.


마치며

이번 포스팅에서는 대부분 잘못 알고있는 프로그래밍 조언들에 관해 알아보았다. 물론 이러한 조언들은 옛날에는 유용했을지 모르지만, 요즘에는 그 효용을 잃어버린지 오래되었다.

우리가 사용하는 프로그래밍 기술은 빠르게 진화하고, 그 특성도 매번 바뀐다. 우리 프로그래머들은 성장하기 위해 생각을 유연하게 하고, 그 특성에 맞는 조언을 익혀야 할 것이다.

profile
이도저도 아닌 개발자

0개의 댓글