본문 바로가기
Study

[스파르타 코딩클럽 / 개발자 취업 필수 개념] 1주차 클린 코드 만들기

by 까다로운오리 2021. 12. 15.

스파르타 강의를 듣고 복습차원에 작성하였지만 일부는 강의자료 코드와 다릅니다..^_^

 

 

 

 

1-1 변수 이름짓기

 

1. 만약 '내 친구'에 대한 변수를 생성할 때 'mf' 와 같이 나만 알아볼 수 있도록 변수명을 설정하지 마라.

2. 적정 변수의 길이는 30글자 이하로, 누구든 그 변수가 어떤 변수인지 알아볼 수 있게 설정한다.

3. 넘겨받을 인자는 from을 내포할 수 있기에 인자또한 누구든 알아볼 수 있게 설정한다.

 

만약 내 친구인지 아닌지 확인하는 코드를 짠다고 했을 때,

 

def is_it(a):
    if a in list:
    	return a

이렇게 코드를 짜는것 보다

def is_friend(people):
    if people in my_friend:
    	return people

 

다음과 같이 가독성이 좋게 코드를 짜야한다.

 

※ Shift + F6을 이용하면 한번에 변수명을 변경할 수 있다.

※ Ctrl+g 를 이용하면 밑의 동일한 변수명까지 함께 수정할 수 있다.

 

 

 

여기서 문제 ( 스파르타 강의자료와 동일)!

def func(obj, var):
    # 버스가 만석인지 확인합니다. 
    # 버스가 만석이라면 함수를 종료하고, 그렇지 않다면 유저가 해당 버스에 탑승합니다.
    if obj.check():
        return
    else:
        var.fun(obj)

    # 탑승해서 나의 나이에 맞게 사용요금을 냅니다.
    if var.prop < 19:
        var.fun2(800)
    elif var.prop > 65:
        pass
    else:
        var.fun2(1300)

    # 만약 버스의 현재 위치가 내가 내리고 싶은 종착지라면 하차합니다.
    if obj.loc == var.dst:
        var.fun3(obj)

 

내 풀이

Min_adult = 19
Old_man = 65
Min_adult_pay = 800
Adult_pay = 1300

def ride_bus(bus, user):
    # 버스가 만석인지 확인합니다. 
    # 버스가 만석이라면 함수를 종료하고, 그렇지 않다면 유저가 해당 버스에 탑승합니다.
    if bus.is_full():
        return
    user.ride_bus(bus)

    # 탑승해서 나의 나이에 맞게 사용요금을 냅니다.
    if user.age < Min_adult :
        user.pay(Min_adult_pay)
    elif user.age > Old_man:
        pass
    else:
        user.pay(Adult_pay)

    # 만약 버스의 현재 위치가 내가 내리고 싶은 종착지라면 하차합니다.
    if bus.location == user.destination:
        user.get_off(bus)

 

※ Boolean형태의 return값은 True,False이기 때문에 is_xxx() 이라는 메소드를 쓰곤 한다.

 

 

 


1-2 함수

 

 

함수의 코드 길이는 최대한 줄이는 것이 좋다.

 

추상화: 중요한 특징을 찾아낸 후 간단하게 표현하는 것. 추상화는 필요한 부분, 중요한 부분을 통합하여 하나로 만드는 것을 말합니다. (출처: 천재학습백과 초등 소프트웨어 용어사전)

 

코드의 추상화수준을 높이는것이 중요하다.

 

추상화 수준이 높다는 것은 디테일을 많이 숨겼다는 것이고, 추상화 수준이 낮다는 것은 디테일이 많이 드러났다고 볼 수 있다.(TMI)

 

예를들어 한 레시피에 대한 코드를 작성할 때 '레시피'라는 함수에 재료,만드는 방법,플레이팅 을 다 나열하는 것 보단

각 분야로 나눈 함수들로 레시피라는 함수를 작성하는 것이 좋다.

 

def 레시피(재료, 방법):
	
    재료준비_함수()
    만드는방법_함수()
    return 플레이팅_함수()

 

※ IDE에서 함수 추출을 지원하고 있는데, 추출하고 싶은 함수를 드래그 한 다음, Refactor -> Extract -> Method 버튼을 누르면 함수 추출이 가능하다.

 


1-3 함수

 

 

1. 부수효과를 일으키지 마라

순수 함수( 동일한 인자를 주었을 때, 항상 같은 값을 리턴하는 함수)를 작성할 때는 외부인자 값에 영향을 받고 영향을 주면 안된다. 값을 설정한다는 함수 내에서 부수효과를 일으키는 것은 상관 없음!

 

2. early return 을 하자.

if else를 사용하여 사전 조건들을 체크하다보면 코드의 중첩단계가 깊어져 가독성 및 유지보수에 좋지 않다. 사전 조건이 거짓이라면 빠르게 반환해서 더이상 다음단계가 실행되지 않도록 코드를 짜는것이 early return!

 

3. 명령과 조회를 나누자. (스파르타 강의자료 코드와 동일)

일반적으로 함수의 역할은 조회, 변경 이다. 이 두가지를 동시에 사용하면 혼돈에 쌓일 수 있는데 함수의 역할을 잘개 쪼개어 의도를 명확히 해야한다.

 

user가 'test'라는 값 설정이 잘 된것임을 확인하는 코드

#의도 불분명
if set_value(user, "test"):


#의도 명확
set_value(user,"test")
if check_value(user,"test"):

 

4. 함수는 이름에 담긴 한가지 일만 하자.

같은 일을 하는 함수가 두개 있으면 안된다. 함수명에 있는 일만 하고 너무 많다면 함수를 쪼개자.

 

 

 


1-4 예외

 

1-4-1. try except

 

 

python의 예외처리는 다음과 같이 작성한다.

try:
    ... # 예외를 발생시킬 수 있는 코드
except [발생 오류[as 오류 메시지 변수]]:
    ... # 발생한 예외를 처리하는 로직

 

try 문에는 finally 문을 쓸 수 있는데 finally는 예외 발생과 상관없이 코드 실행 이후에 후처리가 필요할 때 사용한다.

 

try:
    학교()
    학원()
except:
    PC방()
finally:
    집()

 

 

 

1-4-2. None

만약 다음과 같은 코드가 있다고 가정해보자

def find_password(id, password):
	password = exist_password(id, password)
    if password is not None:
    	...
    else:
    	...


def exist_password(id, password):
	if not id.has_inform():
  		return None
 	return password_change(password)

(코드 정렬이 왜 이렇게  되는지 모르겠지만..)

 

결과값을 None으로 반환해 주면 위와같이 None일 경우 None이 아닐경우 예외처리를 한번 더 해줘야 한다.

해서 이를 해소하기 위해서 raise를 사용하여 예외처리를 해준다.

 

 

1-4-3 리턴 코드 대신 예외를 사용

def find_password(id, password):
	password = exist_password(id, password)
	

def exist_password(password, id):
	if not id.has_inform():
  		raise Exception("존재하지 않는 아이디입니다.")
 	return password_change(password)

이렇게 예외처리를 사용하면 가독성 뿐 아니라 로직과 예외처리 부분을 나눠줄 수 있어 필요한 부분에만 집중할 수 있다.

 

즉, 코드의 응집성을 높여주기 때문에 예외처리를 한다!

 

 

그럼 예외에 따라 동작을 다르게 해야한다면 어떻게 해야 할까?

 

 

첫번째 방법으로 상수 집합을 정의하는 Enum 클래스를 사용해서 반환한다.

from enum import Enum


class ErrorCode(Enum):
    NOT_EXIST_INFORM = 1
    NOT_EXIST_PHONENUMBER = 2


def find_password(id, password, phone_number):
	password = exist_password(id, password, phone_number)
	if password == ErrorCode.NOT_EXIST_INFORM:
            ...  # 아이디 찾기 페이지로 이동
    	elif password == ErrorCode.NOT_EXIST_PHONENUMBER:
            ...  # 비밀번호 찾기 페이지로 이동
            	


def exist_password(password, id, phone_number):
	if not id.has_inform():
  		return ErrorCode.NOT_EXIST_INFORM:
    	elif inform(id, phonenumber) != phone_number
    		return ErrorCode.NOT_EXIST_PHONENUMBER:
 	return password_change(password)

 

 

 

두번째 방법

def find_password(id, password, phone_number):
	try:
		password = exist_password(id, password, phone_number)
	except Exception as e:
    		if e.args[0] == "존재하지 않는 아이디입니다.":
            		...  # 아이디 찾기 페이지로 이동
            		pass
        	elif e.args[0] == "전화번호를 확인해주세요.":
            		...  # 비밀번호 찾기 페이지로 이동
            		pass


def exist_password(password, id, phone_number):
	if not id.has_inform():
  		raise Exception("존재하지 않는 아이디입니다.")
    	elif inform(id, phonenumber) != phone_number
    		raise Exception("전화번호를 확인해주세요.")
 	return password_change(password)

하지만 이 방법도 비효율 적인 코드이다. 에러메세지는 너무나 쉽게 변경 될 가능성이 있기에 정확하게 캐치하지 못할 수 있다. exception은 일반적으로 발생하는 모든 exception을 나타내기에 어떤 exception을 잡을지도 모른다.

 

 

 

해서 자신만의 에러를 만들면 된다.

class MustHaveId(Exception):
	pass

class MustHavePhonenumber(Exception):
	pass


def find_password(id, password, phone_number):
	try:
		password = exist_password(id, password, phone_number)
	except MustHaveId as e:
            ...  # 아이디 찾기 페이지로 이동

	except MustHavePhonenumber as e:
            ...  # 비밀번호 찾기 페이지로 이동
        


def exist_password(password, id, phone_number):
	if not id.has_inform():
  		raise MustHaveId("존재하지 않는 아이디입니다.")
    	elif inform(id, phonenumber) != phone_number
    		raise MustHavePhonenumber("전화번호를 확인해주세요.")
 	return password_change(password)

 

'Study' 카테고리의 다른 글

[ Python ] 이터레이터(Iterator) vs 제너레이터 (Generator)  (1) 2024.03.31