Първо правило: Добри имена на променливи
Променливите обикновенно отговарят за съществуващи обекти/концепции в реалния проблем, който решавате. Това ги прави идеални за комуникиране на идеята на кода. За целта, обаче, се налага да избирате смислени имена.
- Използвайте смислени имена, които да показват ясно и недвусмислено за какво служи променливата
- Спазвайте конвенция в именуването на нещата
- Избягвайте думи, в които лесно се допускат правописни грешки
- Избягвайте криптични съкращения или дълги имена - numberOfPeopleOnTheUsOlympicTeam, npot, teamMemberCount
- Избягвайте като цяло безсмислени имена - thing, stuff, foo
- Бъдете консистенти в наименоването на променливите - без shipsCount и numDocks в една програма.
- Не използвайте една променлива два пъти за едни и същи неща.
Типична грешка
# Грешно
temp = sqrt(b ** 2 - 4 * a * c)
x1 = (-b + temp) / (2 * a)
x2 = (-b - temp) / (2 * a)
# По-правилно
discriminant = sqrt(b ** 2 - 4 * a * c)
x1 = (-b + discriminant) / (2 * a)
x2 = (-b - discriminant) / (2 * a)
Лоши имена
old = read_old()
tpl = get_values("c:/")
tup = {}
for t in tpl:
if old[t] != tpl[t]: continue
tup.update({t:tpl[t]})
show(tup)
save(tpl)
Добри имена
old_hashsums = read_cached_hashsums()
new_hashsums = find_hashsums('c:/')
changed_files = {}
for filename in old_hashsums:
if old_hashsums[filename] != new_hashsums[filename]:
changed_files[filename] = new_hashsums[filename]
report_changes(changed_files)
save_hashsums(new_hashsums)
Функция/Метод > Блок с коментар
Функциите са едно от най-често използваните средства в програмирането. И все пак, причините за които има смисъл да създавате функция са.
- Опростяване на кода.
- Избягване на повтаряне на код.
- Скриване на последователни действия.
- Разширяемост.
- За по-гъвкаво наследяване.
- Изолиране на сложността.
- Скриване на имплементационни детайли.
- Като цяло: за създаване на абстракция.
Именуване на функции
При именуване на рутини се съобразявайте внимателно със следните неща.
- Да обяснява всичко което функцията прави
- Избягвайте безсмислени и размити имена - doStuff(), generateData(), processInput().
- Не различавайте две рутини само по число - wait2() и wait3().
- Ако функцията връща стойност, кръстете я така че да описва връщаната стойност
- Ако функцията е „процедура“, използвайте глагол в името й, който да описва действието й.
- Използвайте противоположни имена - add/remove, open/close, get/set - консистентно.
Как пишем документация
# Това е моята функция, която променя флукса на
# кондензатора
def modify_flux_capacitor(new_value):
...
Не.
Как пишем документация
def modify_flux_capacitor(new_value):
# Това е моята функция, която променя флукса на
# кондензатора
...
Не.
Как пишем документация
def modify_flux_capacitor(new_value):
"""Това е моята функция, която променя флукса на
кондензатора."""
...
Здравейте, docstrings!
some_function.__doc__
.pyc
- .pyc са прекомпилирани версии на .py файловете ни
- В Python 3 стоят в директорията __pycache__
- В Python 2 стоят до .py файловете
- Не мислим за тях
- (освен при version control)
Модули
- Всеки файл е модул
- Всяка директория е модул, ако има __init__.py
- __init__.py често е празен, но не е задължително
Модули
# delorean/__init__.py
def time_travel_possible(speed):
return speed >= 88
# mcfly.py
from delorean import time_travel_possible
Модули
from module import a, b, c
a()
Модули
from module import a, b, c
a()
from module import a as b
b()
Модули
from module import a, b, c
a()
from module import a as b
b()
from django.contrib.auth.models import User
my_user = User()
Модули
from module import a, b, c
a()
from module import a as b
b()
from django.contrib.auth.models import User
my_user = User()
from module import * # не се препоръчва
Модули
from django.db import models
my_model = models.Model()
Модули
import datetime
now = datetime.datetime.now()
import os, sys
sys.path.append('/h4x0r')
Кохезия
„Кохезията“ на една рутина смътно описва действието й. Като говорим за „добра кохезия“ имаме предвид, че една рутина прави едно единствено нещо и го прави добре. Най-силния вид „кохезия“ е функционалната.
Приемливи видове кохезия
- Последователна кохезия - рутината капсулира няколко действия, които трябва да се направят последователно.
- Комуникационна кохезия - рутината извършва няколко различни операции над едни и същи данни
- Времева козехия - рутината извършва няколко действия, които трябва да станат едновременно - Startup(), Shutdown()
Неприемливи видове кохезия
- Процедурна - когато рутината е създадена само защото това отговаря на последователността в която потребителя извършва действията.
- Логическа - поведението на рутината зависи силно от стойността на някой параметър.
- Случайна - когато действията в рутината не са особено свързани.
Аргументи на функциите
- Действието на една рутина не трябва да зависи от стойностите на неин аргумент.
- Старайте се да не ползвате повече от 7 (седем) аргумента в една рутина
- Когато извиквате рутина с много аргументи, хубаво е да ползвате възможността на Python да предава аргументите наименовано.
- Не променяйте състоянието на параметрите на функциите, ако може да го избегнете.
- Ако ползвате параметри за изход, тогава избягвайте да ги ползвате и като входни.
- Ако евентуално имате нужда от параметри за вход, вход-изход и изход, подреждайте ги консистентно в програмата си.
Не ползвайте глобални променливи
Когато пишете функции, не ползвайте глобални променливи. Ама въобще. Най-честия случай на неправилно ползване на глобавни променливи е когато се употребяват за комуникация между функции. Никога не правете това. В рядките случаи, в които имате нужда от „глобални“ обекти правете Singleton-и или thread.local-и.
Не ползвайте goto
В Python няма goto. Ако случайно пишете на друг език, в който има goto, това правило остава - не ползвайте goto.
Не използвайте глупави низове в съобщенията за грешка
Еквивалентност
- Observational equivalence
- Behavior equivalence
Имате два обекта. Те са равни ако…
Observational equivalence
…с произволна поредица от observer методи не може да разберете дали те са различни или не.
Behavioral equivalence
…с произволна поредица от observer-и и мутатори не може да разберете дали те са различни или не.
Design by Contract
За всеки метод се дефинира следното:
- Предусловие (precondition) - условие, което трябва да бъде изпълнено за да може клиентът да извика този метод
- Постусловие (postcondition) - условие, гарантирано да бъде изпълнено след приключването на метода
- Инвариант (invariant) - условие, което трябва да бъде изпълнено по време на изпълнение на метода
Наследяването е зло
- Като състоянието. Унищожава планети.
- Композицията е по-яка.
- Не ни вярвате?
Кой кого?
class Rectangle:
def a(self): ...
def b(self): ...
def setA(self, a): ...
def setB(self, b): ...
class Square:
def a(self): ...
def setSide(self, side): ...
Liskov's Substitution Principle
Клас Б може да наследи от клас А, само ако на всички места на които може да използвате инстанция на А може да използвате инстанция на Б.
Liskov's Substitution Principle (2)
В термините на Design by Contract Б може да наследи А ако:
- Дефинира толкова или по-малко строги предусловия от А
- Дефинира толкова или по-строги постусловия от А
- Запазва инвариантите на А
Design Patterns
The second coming of Jesus.