مقدمه
اصول مهندسی نرم افزار، از کتاب کد تمیز نوشتهی 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
)
)
ادامه دارد… :)