지저분한 파이썬 작성을 중지하십시오 : 깨끗한 코드 충돌 코스

지저분한 파이썬 작성을 중지하십시오 : 깨끗한 코드 충돌 코스

지저분한 파이썬 작성을 중지하십시오 : 깨끗한 코드 충돌 코스
저자의 이미지 | 표의 문자

Python에서 잠시 동안 코딩 한 경우 기본 사항을 마스터하고 몇 가지 프로젝트를 구축했을 것입니다. 그리고 지금 당신은 코드 생각을보고 있습니다. “이것은 효과가 있지만 … 코드 검토에서 자랑스럽게 보여주는 것은 아닙니다.” 우리는 모두 거기에있었습니다.

그러나 계속 코딩 할 때 클린 코드를 작성하는 것이 기능 코드를 작성하는 것만 큼 중요해집니다. 이 기사에서는 “It Runs, Touch It”에서 “이것은 실제로 유지 가능하다”는 실용적인 기술을 컴파일했습니다.

🔗 Github의 코드 링크

1. 모델 데이터를 명시 적으로. dicts 주위를지나 가지 마십시오

사전은 파이썬에서 매우 유연하며 이것이 바로 문제입니다. 코드 전반에 걸쳐 원시 사전을 전달하면 오타, 주요 오류 및 실제로 존재 해야하는 데이터에 대한 혼란을 초대합니다.

대신 :

def process_user(user_dict):
    if user_dict['status'] == 'active':  # What if 'status' is missing?
        send_email(user_dict['email'])   # What if it's 'mail' in some places?
        
        # Is it 'name', 'full_name', or 'username'? Who knows!
        log_activity(f"Processed {user_dict['name']}")

이 코드는 사전 키가 검증없이 존재한다고 가정하기 때문에 강력하지 않습니다. 오타 나 누락 된 키에 대한 보호를 제공하지 않으므로 KeyError 런타임 예외. 어떤 필드가 예상되는지에 대한 문서도 없습니다.

이것을 수행하십시오 :

from dataclasses import dataclass
from typing import Optional

@dataclass
class User:
    id: int
    email: str
    full_name: str
    status: str
    last_login: Optional[datetime] = None

def process_user(user: User):
    if user.status == 'active':
        send_email(user.email)
        log_activity(f"Processed {user.full_name}")

파이썬 @dataclass 데코레이터는 최소 보일러 플레이트가있는 깨끗하고 명시적인 구조를 제공합니다. IDE는 이제 속성에 대해 자동 완성을 제공 할 수 있으며 필요한 필드가 누락 된 경우 즉각적인 오류가 발생합니다.

보다 복잡한 검증을 위해 Pydantic을 고려하십시오.

from pydantic import BaseModel, EmailStr, validator

class User(BaseModel):
    id: int
    email: EmailStr  # Validates email format
    full_name: str
    status: str
    
    @validator('status')
    def status_must_be_valid(cls, v):
        if v not in {'active', 'inactive', 'pending'}:
            raise ValueError('Must be active, inactive or pending')
        return v

이제 데이터가 자체를 검증하고 일찍 오류를 포착하며 기대치를 명확하게 문서화합니다.

2. 알려진 선택에 열거를 사용하십시오

문자열 리터럴은 오타가 발생하기 쉬우 며 IDE AutoComplete를 제공하지 않습니다. 유효성 검사는 런타임에만 발생합니다.

대신 :

def process_order(order, status):
    if status == 'pending':
        # process logic
    elif status == 'shipped':
        # different logic
    elif status == 'delivered':
        # more logic
    else:
        raise ValueError(f"Invalid status: {status}")
        
# Later in your code...
process_order(order, 'shiped')  # Typo! But no IDE warning

이것을 수행하십시오 :

from enum import Enum, auto

class OrderStatus(Enum):
    PENDING = 'pending'
    SHIPPED = 'shipped'
    DELIVERED = 'delivered'
    
def process_order(order, status: OrderStatus):
    if status == OrderStatus.PENDING:
        # process logic
    elif status == OrderStatus.SHIPPED:
        # different logic
    elif status == OrderStatus.DELIVERED:
        # more logic
    
# Later in your code...
process_order(order, OrderStatus.SHIPPED)  # IDE autocomplete helps!

고정 된 옵션 세트를 다룰 때 열거는 코드가 더욱 강력하고 자체 문서화를 만듭니다.

열거 :

  • 귀하의 IDE는 자동 완성 제안을 제공합니다
  • 오타는 (거의) 불가능 해집니다
  • 필요한 경우 가능한 모든 값을 반복 할 수 있습니다

열거는 명명 된 상수 세트를 만듭니다. 유형 힌트 status: OrderStatus 예상 매개 변수 유형을 문서화합니다. 사용 OrderStatus.SHIPPED 문자열 문자 대신 IDE AutoComplete를 허용하고 개발 시간에 오타를 잡습니다.

3. 명확성을 위해 키워드 전용 인수를 사용하십시오

Python의 유연한 인수 시스템은 강력하지만 기능 호출에 여러 선택적 매개 변수가있을 때 혼란을 초래할 수 있습니다.

대신 :

def create_user(name, email, admin=False, notify=True, temporary=False):
    # Implementation
    
# Later in code...
create_user("John Smith", "[email protected]", True, False)

잠깐, 그 부울은 다시 무엇을 의미합니까?

위치 인수로 호출되면 함수 정의를 확인하지 않고 부울 값이 무엇을 나타내는지는 불분명합니다. admin, 알림 또는 다른 것에 맞습니까?

이것을 수행하십시오 :

def create_user(name, email, *, admin=False, notify=True, temporary=False):
    # Implementation

# Now you must use keywords for optional args
create_user("John Smith", "[email protected]", admin=True, notify=False)

*, 구문은 키워드로 지정된 후 모든 인수를 강제합니다. 이렇게하면 기능이 자체 문서화를 호출하고 독자가 함수 정의를 읽지 않고 참 또는 거짓이 무엇을 참조 할 수 있는지 알 수없는 “미스터리 부울”문제를 방지합니다.

이 패턴은 호출 사이트에서 명확성을 보장하려는 API 통화 등에 특히 유용합니다.

4. os.path에 pathlib를 사용하십시오

Python의 OS.Path 모듈은 기능적이지만 어색합니다. 최신 Pathlib 모듈은 더 직관적이고 오류가 적은 객체 지향적 접근법을 제공합니다.

대신 :

import os

data_dir = os.path.join('data', 'processed')
if not os.path.exists(data_dir):
    os.makedirs(data_dir)

filepath = os.path.join(data_dir, 'output.csv')
with open(filepath, 'w') as f:
    f.write('results\n')
    
# Check if we have a JSON file with the same name
json_path = os.path.splitext(filepath)[0] + '.json'
if os.path.exists(json_path):
    with open(json_path) as f:
        data = json.load(f)

이것은 문자열 조작을 사용합니다 os.path.join() 그리고 os.path.splitext() 경로 처리 용. 경로 작업은 다른 기능에 따라 흩어져 있습니다. 코드는 장황하고 직관적이지 않습니다.

이것을 수행하십시오 :

from pathlib import Path

data_dir = Path('data') / 'processed'
data_dir.mkdir(parents=True, exist_ok=True)

filepath = data_dir / 'output.csv'
filepath.write_text('results\n')

# Check if we have a JSON file with the same name
json_path = filepath.with_suffix('.json')
if json_path.exists():
    data = json.loads(json_path.read_text())

Pathlib가 더 나은 이유 :

  • 통과하는 경로는 / 더 직관적입니다
  • 와 같은 방법 mkdir(),,, exists()그리고 read_text() 경로 객체에 첨부됩니다
  • 변화하는 확장 (with_suffix)과 같은 작업이 더 의미 적입니다

Pathlib는 다른 운영 체제에서 경로 조작의 미묘함을 처리합니다. 이로 인해 코드가 더 휴대하고 강력 해집니다.

5. 가드 조항으로 빠르게 실패하십시오

깊이 중첩 된 if 진술은 종종 이해하고 유지하기가 어렵습니다. 가드 조항 (Guard Clauses)을 사용하면 더 읽기 쉬운 코드가 발생합니다.

대신 :

def process_payment(order, user):
    if order.is_valid:
        if user.has_payment_method:
            payment_method = user.get_payment_method()
            if payment_method.has_sufficient_funds(order.total):
                try:
                    payment_method.charge(order.total)
                    order.mark_as_paid()
                    send_receipt(user, order)
                    return True
                except PaymentError as e:
                    log_error(e)
                    return False
            else:
                log_error("Insufficient funds")
                return False
        else:
            log_error("No payment method")
            return False
    else:
        log_error("Invalid order")
        return False

깊은 둥지는 따라 가기가 어렵습니다. 각 조건부 블록은 여러 가지를 동시에 추적해야합니다.

이것을 수행하십시오 :

def process_payment(order, user):
    # Guard clauses: check preconditions first
    if not order.is_valid:
        log_error("Invalid order")
        return False
        
    if not user.has_payment_method:
        log_error("No payment method")
        return False
    
    payment_method = user.get_payment_method()
    if not payment_method.has_sufficient_funds(order.total):
        log_error("Insufficient funds")
        return False
    
    # Main logic comes after all validations
    try:
        payment_method.charge(order.total)
        order.mark_as_paid()
        send_receipt(user, order)
        return True
    except PaymentError as e:
        log_error(e)
        return False

가드 조항은 오류 케이스를 앞쪽으로 처리하여 들여 쓰기 수준을 줄입니다. 각 조건은 순차적으로 점검하여 흐름을 쉽게 따라갈 수 있도록합니다. 주요 논리는 끝에 오류 처리와 명확하게 분리됩니다.

이 접근법은 논리가 복잡 해짐에 따라 훨씬 더 잘 확정됩니다.

6. 목록 이해력을 과도하게 사용하지 마십시오

목록 이해는 Python의 가장 우아한 기능 중 하나이지만 복잡한 조건이나 변환으로 과부하가 걸릴 때 읽을 수 없습니다.

대신 :

# Hard to parse at a glance
active_premium_emails = [user['email'] for user in users_list 
                         if user['status'] == 'active' and 
                         user['subscription'] == 'premium' and 
                         user['email_verified'] and
                         not user['email'] in blacklisted_domains]

이 목록 이해력은 한 줄에 너무 많은 논리를 포장합니다. 읽고 디버그하기가 어렵습니다. 여러 조건이 함께 연결되어 필터 기준을 이해하기가 어렵습니다.

이것을 수행하십시오 :
더 나은 대안이 있습니다.

옵션 1 : 설명 이름이있는 기능

복잡한 조건을 설명 이름으로 명명 된 함수로 추출합니다. 목록 이해력은 이제 필터링 방법보다는 (이메일 추출)에 중점을 두어 훨씬 더 명확합니다.

def is_valid_premium_user(user):
    return (user['status'] == 'active' and
            user['subscription'] == 'premium' and
            user['email_verified'] and
            not user['email'] in blacklisted_domains)

active_premium_emails = [user['email'] for user in users_list if is_valid_premium_user(user)]

옵션 2 : 논리가 복잡 할 때 전통적인 루프

명확성을 위해 초기 지속과 함께 전통적인 루프를 사용합니다. 각 조건은 별도로 점검하여 어떤 조건이 고장 될 수 있는지 쉽게 디버그 할 수 있습니다. 변환 논리도 명확하게 분리되어 있습니다.

active_premium_emails = []
for user in users_list:
    # Complex filtering logic
    if user['status'] != 'active':
        continue
    if user['subscription'] != 'premium':
        continue
    if not user['email_verified']:
        continue
    if user['email'] in blacklisted_domains:
        continue
        
    # Complex transformation logic
    email = user['email'].lower().strip()
    active_premium_emails.append(email)

목록 이해는 코드를 더 읽기 쉽게 만들어야합니다. 논리가 복잡해지면 :

  • 복잡한 조건을 명명 된 기능으로 나눕니다
  • 조기 지속과 함께 일반 루프를 사용하는 것을 고려하십시오
  • 복잡한 작업을 여러 단계로 나눕니다

목표는 가독성입니다.

7. 재사용 가능한 순수한 기능을 작성하십시오

함수는 항상 동일한 입력에 대해 동일한 출력을 생성하는 경우 순수한 기능입니다. 또한 부작용이 없습니다.

대신 :

total_price = 0  # Global state

def add_item_price(item_name, quantity):
    global total_price
    # Look up price from global inventory
    price = inventory.get_item_price(item_name)
    # Apply discount 
    if settings.discount_enabled:
        price *= 0.9
    # Update global state
    total_price += price * quantity
    
# Later in code...
add_item_price('widget', 5)
add_item_price('gadget', 3)
print(f"Total: ${total_price:.2f}")

이것은 글로벌 상태를 사용합니다 (total_price) 테스트가 어려워집니다.

이 기능에는 부작용 (글로벌 상태 수정)이 있으며 외부 상태 (인벤토리 및 설정)에 따라 다릅니다. 이것은 예측할 수없고 재사용하기 어렵게 만듭니다.

이것을 수행하십시오 :

def calculate_item_price(item, price, quantity, discount=0):
    """Calculate final price for a quantity of items with optional discount.
    
    Args:
        item: Item identifier (for logging)
        price: Base unit price
        quantity: Number of items
        discount: Discount as decimal 
        
    Returns:
        Final price after discounts
    """
    discounted_price = price * (1 - discount)
    return discounted_price * quantity

def calculate_order_total(items, discount=0):
    """Calculate total price for a collection of items.
    
    Args:
        items: List of (item_name, price, quantity) tuples
        discount: Order-level discount
        
    Returns:
        Total price after all discounts
    """
    return sum(
        calculate_item_price(item, price, quantity, discount)
        for item, price, quantity in items
    )

# Later in code...
order_items = [
    ('widget', inventory.get_item_price('widget'), 5),
    ('gadget', inventory.get_item_price('gadget'), 3),
]

total = calculate_order_total(order_items, 
                             discount=0.1 if settings.discount_enabled else 0)
print(f"Total: ${total:.2f}")

다음 버전은 모든 종속성을 매개 변수로 사용하는 순수한 기능을 사용합니다.

8. 공공 기능과 클래스에 대한 문서를 작성하십시오

문서화는 나중에 생각하지 않아야합니다. 관리 가능한 코드의 핵심 부분입니다. 좋은 docstrings는 기능이 무엇을하는지뿐만 아니라 존재하는 이유와 올바르게 사용하는 방법을 설명합니다.

대신 :

def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return celsius * 9/5 + 32

이것은 함수 이름 만 반복하는 최소한의 문서입니다. 매개 변수, 반환 값 또는 에지 케이스에 대한 정보는 제공하지 않습니다.
이것을 수행하십시오 :

def celsius_to_fahrenheit(celsius):
	"""
	Convert temperature from Celsius to Fahrenheit.
	The formula used is: F = C × (9/5) + 32
	Args:
    	celsius: Temperature in degrees Celsius (can be float or int)
	Returns:
    	Temperature converted to degrees Fahrenheit
	Example:
    	>>> celsius_to_fahrenheit(0)
    	32.0
    	>>> celsius_to_fahrenheit(100)
    	212.0
    	>>> celsius_to_fahrenheit(-40)
    	-40.0
	"""
	return celsius * 9/5 + 32

좋은 docstring :

  • 문서 매개 변수 및 반환 값
  • 제기 될 수있는 예외를 기록합니다
  • 사용 예제를 제공합니다

DocStrings는 코드와 동기화되는 실행 가능한 문서 역할을합니다.

9. 라인 및 서식을 자동화합니다

스타일 문제와 일반적인 버그를 포착하기 위해 수동 검사에 의존하지 마십시오. 자동화 된 도구는 코드 품질과 일관성을 보장하는 지루한 작업을 처리 할 수 ​​있습니다.

이 라인팅 및 서식 도구를 설정해 볼 수 있습니다.

  1. 검은색 – 코드 포맷터
  2. 주름 옷깃 – 거의 Linner
  3. Mypy – 정적 유형 검사기
  4. ISORT – 주최자 가져 오기

사전 커밋 후크를 사용하여 통합하여 각 커밋 전에 코드를 자동으로 확인하고 포맷하십시오.

  1. 사전 커밋 설치 : pip install pre-commit
  2. a .pre-commit-config.yaml 도구가 구성된 파일
  3. 달리다 pre-commit install 활성화

이 설정은 일관된 코드 스타일을 보장하고 수동 노력없이 일찍 오류를 포착합니다.

더 나은 파이썬 코드를 작성하여 더 많은 것을 알 수 있도록 7 개의 도구를 확인할 수 있습니다.

10

일반적인 예외 처리기는 버그를 숨기고 디버깅을 어렵게 만듭니다. 구문 오류, 메모리 오류 및 키보드 인터럽트를 포함하여 모든 것을 포착합니다.

대신 :

try:
    user_data = get_user_from_api(user_id)
    process_user_data(user_data)
    save_to_database(user_data)
except:
    # What failed? We'll never know!
    logger.error("Something went wrong")

이것은 예외를 사용하여 처리합니다.

  • 프로그래밍 오류 (구문 오류와 같은)
  • 시스템 오류 (MemoryError와 같은)
  • 키보드 인터럽트 (Ctrl+C)
  • 예상 오류 (네트워크 타임 아웃 예 🙂

따라서 모든 오류가 동일하게 처리되므로 디버깅을 매우 어렵게 만듭니다.

이것을 수행하십시오 :

try:
    user_data = get_user_from_api(user_id)
    process_user_data(user_data)
    save_to_database(user_data)
except ConnectionError as e:
    logger.error(f"API connection failed: {e}")
    # Handle API connection issues
except ValueError as e:
    logger.error(f"Invalid user data received: {e}")
    # Handle validation issues
except DatabaseError as e:
    logger.error(f"Database error: {e}")
    # Handle database issues
except Exception as e:
    # Last resort for unexpected errors
    logger.critical(f"Unexpected error processing user {user_id}: {e}", 
                  exc_info=True)
    # Possibly re-raise or handle generically
    raise

예상하고 적절하게 처리 할 수있는 구체적인 예외를 포착합니다. 각 예외 유형에는 고유 한 오류 메시지 및 처리 전략이 있습니다.

예외를 제외한 최종 최종에는 예상치 못한 오류가 발생하여 전체 추적으로 로그인합니다 (exc_info=True), 심각한 문제를 조용히 무시하지 않도록 그들을 다시 만들어냅니다.

어떤 이유로 든 캐치 핸들러가 필요한 경우 사용하십시오. except Exception as e: 베어보다는 except:그리고 항상 전체 예외 세부 정보를 기록하십시오 exc_info=True.

마무리

코드에서 이러한 관행 중 적어도 일부를 사용하시기 바랍니다. 프로젝트에서 구현을 시작하십시오.

코드가 더욱 유지 가능하고 테스트 가능하며 추론하기 쉬운 것을 알 수 있습니다.

다음에 바로 가기를 신고 싶은 유혹을 받으면 다음을 기억하십시오. 코드는 쓰여진 것보다 더 많이 읽습니다. 행복한 청정 코딩?

발라 프리 야 c 인도의 개발자이자 기술 작가입니다. 그녀는 수학, 프로그래밍, 데이터 과학 및 컨텐츠 제작의 교차점에서 일하는 것을 좋아합니다. 그녀의 관심 분야와 전문 지식에는 DevOps, 데이터 과학 및 자연어 처리가 포함됩니다. 그녀는 독서, 쓰기, 코딩 및 커피를 즐깁니다! 현재 그녀는 자습서, 방법 안내, 의견 조각 등을 통해 개발자 커뮤니티와 지식을 배우고 공유하는 작업을하고 있습니다. Bala는 또한 매력적인 리소스 개요 및 코딩 자습서를 만듭니다.

출처 참조

Post Comment

당신은 놓쳤을 수도 있습니다