04. Добри практики
Писането на код като работа
Когато пишете код, рядко го правите самоцелно. Вместо това, вие опитвате се да решите някакъв реален проблем със собствена логика, терминология и особености. Винаги когато пишете код трябва да се стараете той да отразява много добре този проблем - това го прави много по-четим, по-лесен за поддръжка и по-разбираем от външни хора. Още повече, така вие ясно показвате намерението което вашия код има, вместо да карате читателя да задълбава в особенностите на вашата реализация.
Първо правило: Добри имена на променливи
Променливите обикновенно отговарят за съществуващи обекти/концепции в реалния проблем, който решавате. Това ги прави идеални за комуникиране на идеята на кода. За целта, обаче, се налага да избирате смислени имена.
- Използвайте смислени имена, които да показват ясно и недвусмислено за какво служи променливата
- Спазвайте конвенция в именуването на нещата
- Избягвайте думи, в които лесно се допускат правописни грешки
- Избягвайте криптични съкращения или дълги имена - 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 - консистентно.
Кохезия
„Кохезията“ на една рутина смътно описва действието й. Като говорим за „добра кохезия“ имаме предвид, че една рутина прави едно единствено нещо и го прави добре. Най-силния вид „кохезия“ е функционалната.
Приемливи видове кохезия
- Последователна кохезия - рутината капсулира няколко действия, които трябва да се направят последователно.
- Комуникационна кохезия - рутината извършва няколко различни операции над едни и същи данни
- Времева козехия - рутината извършва няколко действия, които трябва да станат едновременно - 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.
Version Control System
Системите за контрол на версиите не са добра практика. Те са задължителна практика!
Колко от вас са чували за тези?
- CVS
- SVN
- Bazaar
- Perforce
- Microsoft SourceSafe (rofl)
- Mercurial
- Git
За чий ни е контрол на версиите?
История - Пълна видимост над промените
- Кое и кога е променено?
- И най-вече...кой?
- Понякога и защо?
Бранчове
- Пясъчник(sandbox) за промени
- Пазим си неготовите парчета код
- Бързо switch-ваме между тях
Пазарът и катедралата
Git and GitHub
Линукс е второто най-добро нещо, създадено от Линус Торвалдс
Още въпроси?
- Пишете ни на fmi@py-bg.net
- Страница на курса: http://fmi.py-bg.net/
- Форуми на курса: http://fmi.py-bg.net/topics
- Курсът в Twitter: http://twitter.com/pyfmi
- Курсът във Facebook: http://www.facebook.com/group.php?gid=104970619536589