اصول کد نویسی تمیز - بخش اول

اصول کد نویسی تمیز - بخش اول

مقدمه

اصول مهندسی نرم افزار، از کتاب کد تمیز نوشته‌ی Robert C. Martin برای پایتون. در این سری از مقالات یاد میگیرید که چطوری یک نرم افزار خوانا (readable)، قابل استفاده مجدد (reusable) و قابل از نو بازسازی (refactorable) تولید کنید.

متغیر ها 


از اسم های متغیر با معنا و قابل تلفظ استفاده کنید

بد:

import datetime

ymdstr = datetime.date.today().strftime("%y-%m-%d")

نیازی به استفاده از تایپ str برای نام متغیر نیست.

خوب:

import datetime

current_date: str = datetime.date.today().strftime("%y-%m-%d")

از واژگان یکسان برای همان نوع متغیر استفاده کنید

بد:

ما در اینجا از چند اسم برای یک موجودیت استفاده میکنیم

def get_user_info(): pass
def get_client_data(): pass
def get_customer_record(): pass

خوب:

اگر موجودیت یکسانی دارید، باید از همان نام برای توابع خود استفاده کنید

def get_user_info(): pass
def get_user_data(): pass
def get_user_record(): pass

بهتر:

پایتون همچنین یک زبان برنامه نویسی شیءگراست. اگر منطقی است، توابع را با پیاده سازی مشخص از موجودیت خود، به عنوان ویژگی های نمونه بنویسید

from typing import Union, Dict

class Record:
    pass

class User:
    info : str

    @property
    def data(self) -> Dict[str, str]:
        return {}

    def get_record(self) -> Union[Record, None]:
        return Record()

از نام های قابل جستجو استفاده کنید

ما بیشتر از آنکه کد بنویسیم، آن ها را میخوانیم. بنابراین مهم است کدی که می‌نویسیم خوانا و قابل جستجو باشد. نام گذاری نکردن متغیر هایی که برنامه ما را قابل فهم می‌کنند، باعث نارضایتی خواننده از کد ما می‌شود. پس نام هایتان را قابل جستجو کنید.

بد:

import time

# What is the number 86400 for again?
time.sleep(86400)

خوب:

import time

# Declare them in the global namespace for the module.
SECONDS_IN_A_DAY = 60 * 60 * 24
time.sleep(SECONDS_IN_A_DAY)

از متغیر های توضیحی استفاده کنید

بد:

import re

address = "One Infinite Loop, Cupertino 95014"
city_zip_code_regex = r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$"

matches = re.match(city_zip_code_regex, address)
if matches:
    print(f"{matches[1]}: {matches[2]}")

بهتر است:

اما هنوز به به رجکس وابسته ایم

import re

address = "One Infinite Loop, Cupertino 95014"
city_zip_code_regex = r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$"
matches = re.match(city_zip_code_regex, address)

if matches:
    city, zip_code = matches.groups()
    print(f"{city}: {zip_code}")

خوب:

وابستگی به رجکس را با نام گذاری الگو های فرعی کمتر کنید

import re

address = "One Infinite Loop, Cupertino 95014"
city_zip_code_regex = r"^[^,\\]+[,\\\s]+(?P<city>.+?)\s*(?P<zip_code>\d{5})?$"

matches = re.match(city_zip_code_regex, address)
if matches:
    print(f"{matches['city']}, {matches['zip_code']}")

از نقشه برداری ذهنی خودداری کنید

خواننده‌ی کدتان را مجبور نکنید که نام های متغیر های شما را ترجمه کند. صریح بهتر از ضمنی است!

بد:

seq = ("Austin", "New York", "San Francisco")

for item in seq:
    # do_something
	# ...
    # Wait, what's `item` again?
    print(item)

خوب:

locations = ("Austin", "New York", "San Francisco")

for location in locations:
    # do_something
    # ...
    print(location)

توضیحات اضافی ننویسید

اگر اسم کلاس یا شیء شما چیزی را میگوید، نیازی نیست که آنرا در اسامی متغیر هایتان تکرار کنید.

بد:

class Car:
    car_make: str
    car_model: str
    car_color: str

خوب:

class Car:
    make: str
    model: str
    color: str

از آرگومان های پیشفرض به جای شروط یک خطی استفاده کنید

بد:

import hashlib

def create_micro_brewery(name):
    name = "Hipster Brew Co." if name is None else name
    slug = hashlib.sha1(name.encode()).hexdigest()
    # etc.

وقتی از آرگومان پیشفرض استفاده می‌کنید، خواننده را کاملا روشن می‌کنید که شما برای آرگومان‌تان یک رشته می‌خواهید.

خوب:

import hashlib

def create_micro_brewery(name: str = "Hipster Brew Co."):
    slug = hashlib.sha1(name.encode()).hexdigest()
    # etc.

آرگومان های توابع (ترجیحاً دو یا کمتر)

کم کردن مقادیر پارامتر های توابع بسیار مهم است چون تست کردن آن‌ها را راحت تر می‌کند. بیشتر از سه تا، مناسب نیست، چون باید حالت های مختلف را با آرگومان های متفاوت تست کنید.

نداشتن هیچ آرگومانی، حالت ایده‌آل هست. یکی یا دو تا خوب، و باید از سه تا اجتناب کرد! هرچیزی بیشتر از آن باید یک رقم شود. معمولا اگر بیشتر از دو آرگومان دارید، کد شما دارد چند کار را باهم انجام می‌دهد. در مواقعی که اینطور نیست، اغلب اوقات یک شیء سطح بالاتر به عنوان یک آرگومان کافی است.

بد:

def create_menu(title, body, button_text, cancellable):
    pass

به سبک جاوا:

class Menu:
    def __init__(self, config: dict):
        self.title = config["title"]
        self.body = config["body"]
        # ...

menu = Menu(
    {
        "title": "My Menu",
        "body": "Something about my menu",
        "button_text": "OK",
        "cancellable": False
    }
) 

خوب:

class MenuConfig:
    """A configuration for the Menu.

    Attributes:
        title: The title of the Menu.
        body: The body of the Menu.
        button_text: The text for the button label.
        cancellable: Can it be cancelled?
    """
    title: str
    body: str
    button_text: str
    cancellable: bool = False


def create_menu(config: MenuConfig) -> None:
    title = config.title
    body = config.body
    # ...


config = MenuConfig()
config.title = "My delicious menu"
config.body = "A description of the various items on the menu"
config.button_text = "Order now!"
# The instance attribute overrides the default class attribute.
config.cancellable = True

create_menu(config)

تفننی:

from typing import NamedTuple

class MenuConfig(NamedTuple):
    """A configuration for the Menu.

    Attributes:
        title: The title of the Menu.
        body: The body of the Menu.
        button_text: The text for the button label.
        cancellable: Can it be cancelled?
    """
    title: str
    body: str
    button_text: str
    cancellable: bool = False


def create_menu(config: MenuConfig):
    title, body, button_text, cancellable = config
    # ...


create_menu(
    MenuConfig(
        title="My delicious menu",
        body="A description of the various items on the menu",
        button_text="Order now!"
    )
)

تفننی تر:

from dataclasses import astuple, dataclass

@dataclass
class MenuConfig:
    """A configuration for the Menu.

    Attributes:
        title: The title of the Menu.
        body: The body of the Menu.
        button_text: The text for the button label.
        cancellable: Can it be cancelled?
    """
    title: str
    body: str
    button_text: str
    cancellable: bool = False

def create_menu(config: MenuConfig):
    title, body, button_text, cancellable = astuple(config)
    # ...


create_menu(
    MenuConfig(
        title="My delicious menu",
        body="A description of the various items on the menu",
        button_text="Order now!"
    )
)

باز هم تفننی تر (فقط Python +3.8)

from typing import TypedDict

class MenuConfig(TypedDict):
    """A configuration for the Menu.

    Attributes:
        title: The title of the Menu.
        body: The body of the Menu.
        button_text: The text for the button label.
        cancellable: Can it be cancelled?
    """
    title: str
    body: str
    button_text: str
    cancellable: bool


def create_menu(config: MenuConfig):
    title = config["title"]
    # ...


create_menu(
    # You need to supply all the parameters
    MenuConfig(
        title="My delicious menu",
        body="A description of the various items on the menu",
        button_text="Order now!",
        cancellable=True
    )
)

 

ادامه دارد… :)

از بهترین نوشته‌های کاربران سکان آکادمی در سکان پلاس


online-support-icon