събота, 17 януари 2009 г.

Някои добри практики в Python

„ Програмиране с Python“, ФМИ
Стефан Кънев & Николай Бачийски
30.05.2007г.
Какво прави следния код?


for x in range(0, 12):
print arr[x]

А този?


fox index in range(0, NUMBER_OF_MONTHS):
print monthNames[index]

Писането на код като работа

Когато пишете код, рядко го правите самоцелно. Вместо това, вие опитвате се да решите някакъв реален проблем със собствена логика, терминология и особености. Винаги когато пишете код трябва да се стараете той да отразява много добре този проблем - това го прави много по-четим, по-лесен за поддръжка и по-разбираем от външни хора. Още повече, така вие ясно показвате намерението което вашия код има, вместо да карате читателя да задълбава в особенностите на вашата реализация.
Първо правило: Добри имена на променливи

Променливите обикновенно отговарят за съществуващи обекти/концепции в реалния проблем, който решавате. Това ги прави идеални за комуникиране на идеята на кода. За целта, обаче, се налага да избирате смислени имена.

* Използвайте смислени имена, които да показват ясно и недвусмислено за какво служи променливата
* Спазвайте конвенция в именуването на нещата
* Избягвайте думи, в които лесно се допускат правописни грешки
* Избягвайте криптични съкращения или дълги имена - 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 = readOld()
tupple = getValues(r'c:\')
tup = {}
for t in tupple:
if old[t] != tupple[t]: continue
tup.update({t:tupple[t]})

show(tup)
save(tupple)

...и добри имена


oldHashsums = readCachedHashsums()
newHashsums = findHashsums(r'c:\')

changedFiles = {}
for filename in oldHashsums:
if oldHashsums[filename] != newHashsums[filename]:
changedFiles[filename] = newHashsums[filename]

reportChanges(changedFiles)
saveHashsums(newHashsums)

Функции / методи / рутини

Рутините са едно от най-често използваните средства в програмирането. И все пак, причините за които има смисъл да създавате рутина са.

* Опростяване на кода.
* Избягване на повтаряне на код.
* Скриване на последователни действия.
* Разширяемост.
* Подобряване на производителността.
* За по-гъвкаво наследяване.
* Изолиране на сложността.
* Скриване на имплементационни детайли.
* Като цяло: за създаване на абстракция.

Именуване на рутини

При именуване на рутини се съобразявайте внимателно със следните неща.

* Да обяснява всичко което рутината прави
* Избягвайте безсмислени и размити имена - doStuff(), generateData(), processInput().
* Не различавайте две рутини само по имена - wait2() и wait3().
* Ако рутината връща стойност, кръстете я така че да описва връщаната стойност
* Ако рутината е "процедура", използвайте глагол в името й, който да описва действието й.
* Използвайте противоположни имена - add/remove, open/close, get/set - консистентно.

Кохезия

"Кохезията" на една рутина смътно описва действието й. Като говорим за "добра кохезия" имаме предвид, че една рутина прави едно единствено нещо и го прави добре. Най-силния вид "кохезия" е функционалната. Други видове са:

* Последователна кохезия - рутината капсулира няколко действия, които трябва да се направят последователно.
* Комуникационна кохезия - рутината извършва няколко различни операции над едни и същи данни
* Времева козехия - рутината извършва няколко действия, които трябва да станат едновременно - Startup(), Shutdown()

Неприемлив вид кохезия

* Процедурна - когато рутината е създадена само защото това отговаря на последователността в която потребителя извършва действията.
* Логическа - поведението на рутината зависи силно от стойността на някой параметър.
* Случайна - когато действията в рутината не са особено свързани.

Аргументи на рутините

* Действието на една рутина не трябва да зависи от стойностите на неин аргумент.
* Старайте се да не ползвате повече от 7 (седем) аргумента в една рутина
* Когато извиквате рутина с много аргументи, хубаво е да ползвате възможността на Python да предава аргументите наименовано.
* Не променяйте състоянието на параметрите на функциите, ако може да го избегнете.
* Ако ползвате параметри за изход, тогава избягвайте да ги ползвате и като входни.
* Ако евентуално имате нужда от параметри за вход, вход-изход и изход, подреждайте ги консистентно в програмата си.

Реакция при грешни параметри на рутината

Лош подход.


def storePerson(name, age, address):
if not isinsntace(name, str) or len(name) < 6:
raise ValueError("Illegal name")
if not isinstance(age, int, long) or age < 0:
raise ArithmeticError("Illegal age")
if not hasattr(address, "street") or not hassattr(address, "number"):
raise ValueError("Illegal address")

storeData(name, age, address.street, address.number)

Реакция при грешни параметри на рутината (2)

По-добър подход.


def storePerson(name, age, address):
assert long(age) >= 6
assert isinstance(name, str) and len(name) >= 6

storeData(name, age, address.street, address.number)

Не ползвайте глобални променливи

Когато пиеше функции, не ползвайте глобални променливи. Ама въобще. Най-честия случай на неправилно ползване на глобавни променливи е когато се употребяват за комуникация между функции. Никога не правете това. В рядките случаи, в които имате нужда от "глобални" обекти правете Singleton-и или thread.local-и.
Не ползвайте goto

В Python няма goto. Ако случайно пишете на друг език, в който има goto, това правило остава - не ползвайте goto.
Не използвайте глупави низове
в съобщенията за грешка
Основната задача на програмиста: да намалява сложността

Подходи за намаляване на сложността:

* Модуларизация
* Абстракия
* Енкапсулация

Няма коментари:

Публикуване на коментар