Lekce 2 - Nejčastější chyby programátorů - Umíš pojmenovat objekty?
V minulé lekci, Nejčastější chyby programátorů - Umíš pojmenovat proměnné?, jsme si ukázali nejčastější chyby začátečníků, kterých se dopouštějí při pojmenování proměnných.
V dnešním tutoriálu kurzu Best practices pro návrh softwaru si ukážeme první dobré praktiky pro objektově orientované programování. Budeme se zabývat zejména správným pojmenováváním tříd, metod a atributů. Podívejte se, jestli neděláte jednu z nejčastějších chyb.
Milionové ztráty
O dobrých praktikách jsme se již bavili a víme, že nepřehledné programy nejsou vůbec žádná malichernost. Jsou totiž:
- Nesrozumitelné pro ostatní - Když tým 5 programátorů, každý s platem 100.000 Kč měsíčně, stráví 20 % pracovní doby luštěním kódu, stojí to ročně 1.2 milionu Kč!
- Náchylné k chybám - Tržby i malých e-shopů jsou měsíčně obvykle v milionech korun, 1 den nefunkčnosti tedy stojí majitele stovky tisíc Kč, dodavateli hrozí nemalé smluvní pokuty.
- Prakticky nerozšiřitelné - Ve stávající funkčnosti se už nikdo nevyzná a nelze ji rozšířit. Programátorský tým o několika lidech musí vytvořit aplikaci znovu, jsme opět v milionech Kč.
- Netestovatelné, nezabezpečené a takto bychom mohli pokračovat.
Není pochyb, že dobré praktiky jsou pro vývoj softwaru v týmu naprosto zásadní a následky jejich porušení potom naprosto fatální.
Jak správně pojmenovávat třídy, atributy a metody?
V dnešní lekci se budeme zabývat "objektovými součástkami", ze kterých jsou naše aplikace složené. Naučíme se je správně pojmenovat, aby byly přehledné.
Motivační příklad
K doktorovi přijde pacient a říká mu, že má problém se svým orgánem
Přesouvat
. Nefunguje mu bota
. Pacient se zdá být
nějaký popletený a doktorovi trvá poměrně dlouho, než z něj dostane, že
ho píchá v patě a proto si nemůže nazout ani botu. Když konečně vypustí
pacienta z ordinace, zjistí, že byl součástí skupiny a takových jich tam
ještě čeká několik desítek.
Podívejme se ještě na druhý příběh. Programátorovi přidělí
program, který spadne s chybou ve třídě MoveData
, metoda
data()
. Program se zdá být nějaký popletený a programátorovi
trvá dlouho, než zjistí, že se jedná o třídu importující data z
externí zálohy a že se prvně musí ručně zavolat metoda
work()
, která import provede. Když chybu konečně opraví,
objeví se další a zjistí, že v programu je několik desítek tříd a
metod, z jejichž názvu vůbec nepozná, co dělají.
Určitě vidíme paralelu těchto dvou příběhů. Zatímco u lidského těla by nás asi těžko napadlo hovořit o orgánu "přesouvat" a funkci "bota", v programech bohužel není takový problém narazit na objekty pojmenované jako děje a funkce pojmenované jako věci, i když je princip úplně stejný. Není divu, že si Indescriptive naming vysloužilo své místo v šestici nejhorších programátorských praktik STUPID.
Pojmenování tříd
Třídy definují objekty, ze kterých je aplikace složená. Z toho vyplývá několik triviálních pravidel. Názvy tříd:
- jsou podstatná jména! - Jedná se o jednotlivé počitatelné objekty bez ohledu na to, kolik objektů od třídy potom vytvoříme.
- nejsou názvy dějů - Jde o objekty (věci). Třídy tedy
nemůžeme nazvat např.
WorkWithFile
neboPrinting
, ale např.FileManager
neboPrinter
(nebo ještě lépeUserInterface
, protože zřídka kdy tvoříme celou třídu jen na vypsání něčeho). - začínají s velkým písmenem - Každé další slovo má
velké písmeno (notace
PascalCase
a to i v Pythonu). Je tak vidět, že jde o obecnou třídu a ne o její konkrétní instanci. - jsou pojmenované podle toho, jakou součást programu reprezentují, což nemusí být vždy stejné, jako to, co uvnitř dělají.
Ukažme si několik příkladů:
-
✗ Špatně
class employee { class Employees { class Invoiceitem { class WorkingWithFile { class Print { class EnteringData {
✔ Správně
class Employee { class EmployeeManager { class InvoiceItem { class FileDatabase { class UserInterface {
-
✗ Špatně
class employee class Employees class Invoiceitem class WorkingWithFile class Print class EnteringData
✔ Správně
class Employee class EmployeeManager class InvoiceItem class FileDatabase class UserInterface
-
✗ Špatně
class employee { class Employees { class Invoiceitem { class WorkingWithFile { class Print { class EnteringData {
✔ Správně
class Employee { class EmployeeManager { class InvoiceItem { class FileDatabase { class UserInterface {
-
✗ Špatně
class employee: class Employees: class invoice_item: class WorkingWithFile: class Print: class EnteringData:
✔ Správně
class Employee: class EmployeeManager: class InvoiceItem: class FileDatabase: class UserInterface:
-
✗ Špatně
class employee { class Employees { class Invoiceitem { class WorkingWithFile { class Print { class EnteringData {
✔ Správně
class Employee { class EmployeeManager { class InvoiceItem { class FileDatabase { class UserInterface {
-
✗ Špatně
class employee { class Employees { class Invoiceitem { class WorkingWithFile { class Print { class EnteringData {
✔ Správně
class Employee { class EmployeeManager { class InvoiceItem { class FileDatabase { class UserInterface {
-
✗ Špatně
class employee { class Employees { class Invoiceitem { class WorkingWithFile { class Print { class EnteringData {
✔ Správně
class Employee { class EmployeeManager { class InvoiceItem { class FileDatabase { class UserInterface {
Pokud narazíte na program, kde se třída jmenuje
WorkingWithFile
, jeho autor si pravděpodobně jen vygooglil, že
kód se píše do class
, aniž by tušil, že tím založil
nějaký objekt.
Třídy v množném čísle
V množném čísle pojmenováváme třídy velmi zřídka.
Například v Javě takto nalezneme třídu Arrays
. Od té
nevytváříme instance a používáme ji jen pro práci s instancemi třídy
Array
, tedy s poli. Pole setřídíme např. jako:
Arrays.sort(employees);
Osobně by mi mnohem větší smysl dávalo, aby tyto metody měla na sobě
přímo třída Array
a psali jsme tedy:
employees.sort()
Autoři Javy pravděpodobně nechtěli třídu Array
příliš
složitou a tak pro některé metody vytvořili tuto druhou třídu. Výsledný
přínos je diskutabilní. My třídy v množném čísle většinou deklarovat
nebudeme.
Pojmenování tříd v angličtině
Základní kurzy jsou na ITnetwork česky, aby byly lépe stravitelné. Kódy těch pokročilých jsou stejně jako reálné obchodní aplikace anglicky. Pro anglické názvy tříd platí samozřejmě to samé, co jsme řekli výše. Můžeme ovšem snadno udělat následující chyby:
- Jednotná čísla - Když v češtině pojmenujeme třídu
pracující s auty
SpravceAut
, mohl by se nabízet anglický překladCarsManager
. Správně je ovšemCarManager
, jednotné číslo, protožeCar
zde funguje jako přídavné jméno. - Pořadí slov - Na rozdíl od češtiny je podstatné
jméno na konci sousloví, správce aut tedy není
ManagerCars
neboManageCars
, aleCarManager
. Cesta k souboru neníPathFile
(což by byl cestový soubor), aleFilePath
apod.
V angličtině se u tříd často používá koncovka -er
,
např. TaskRunner
, podle toho, co třída dělá.
Ukažme si pár příkladů:
-
✗ Špatně
class CarsManager { class PathFile { class RunTasks {
✔ Správně
class CarManager { class FilePath { class TaskRunner {
-
✗ Špatně
class CarsManager class PathFile class RunTasks
✔ Správně
class CarManager class FilePath class TaskRunner
-
✗ Špatně
class CarsManager { class PathFile { class RunTasks {
✔ Správně
class CarManager { class FilePath { class TaskRunner {
-
✗ Špatně
class CarsManager: class PathFile: class RunTasks:
✔ Správně
class CarManager: class FilePath: class TaskRunner:
-
✗ Špatně
class CarsManager { class PathFile { class RunTasks {
✔ Správně
class CarManager { class FilePath { class TaskRunner {
-
✗ Špatně
class CarsManager { class PathFile { class RunTasks {
✔ Správně
class CarManager { class FilePath { class TaskRunner {
-
✗ Špatně
class CarsManager { class PathFile { class RunTasks {
✔ Správně
class CarManager { class FilePath { class TaskRunner {
Pojmenování atributů
Atributy jsou "proměnné" dané třídy. Pro jejich pojmenování tedy platí úplně ty samé zásady jako pro proměnné, které jsme si již detailně vysvětlovali v lekci Nejčastější chyby programátorů - Umíš pojmenovat proměnné?.
Základním pravidlem opět je, že atributy pojmenováváme podle toho, co je v nich uloženo. Název atributu chápeme v kontextu názvu třídy a nemusí tedy dávat smysl sám o sobě. Z jazykového hlediska jsou názvy atributů:
- podstatná jména (
name
,employees
, ...) - přídavná jména (
disabled
,sent
, ...)
Připomeňme si zde i zbylé zásady:
- všechny atributy pojmenováváme buď anglicky nebo česky bez diakritiky, ale ne kombinací těchto jazyků
- víceslovné atributy pojmenováváme pomocí notace
camelCase
nebosnake_case
v závislosti na jazyku - pokud atribut obsahuje kolekci s více hodnotami (např. pole nebo
List
), je jeho název v množném čísle
Ukažme si opět nějaké příklady názvů atributů tříd:
-
✗ Špatně
private String Name; private boolean send; private String[] phonenumbers; private Button tlačítko; private Address[] uzivatelAddress;
✔ Správně
private String name; private boolean sent; private String[] phoneNumbers; private Button tlacitko; // ideálně button private Address[] userAddresses;
-
✗ Špatně
private string Name; private bool send; private string[] phonenumbers; private Button tlačítko; private Address[] uzivatelAddress;
✔ Správně
private string name; private bool sent; private string[] phoneNumbers; private Button tlacitko; // ideálně button private Address[] userAddresses;
-
✗ Špatně
private: string Name; bool send; string* phonenumbers; Button tlačítko; Address* uzivatelAddress;
✔ Správně
private: string name; bool sent; string* phoneNumbers; Button tlacitko; // ideálně button Address* userAddresses;
-
✗ Špatně
self._Name = "" self._send = False self._phonenumbers = [] self._tlačítko = Button() self._uzivatel_address = []
✔ Správně
self._name = "" self._sent = False self._phone_numbers = [] self._tlacitko = Button() # ideálně _button self._user_addresses = []
-
✗ Špatně
private var Name: String private var send: Bool private var phonenumbers: [String]() private var tlačítko: Button private var uzivatelAddress: [Address]()
✔ Správně
private var name: String private var sent: Bool private var phoneNumbers: [String]() private var tlacitko: Button // ideálně button private var userAddresses: [Address]()
-
✗ Špatně
private string $Name; private bool $send; private array $phonenumbers; private Button $tlačítko; private array $uzivatelAddress;
✔ Správně
private string $name; private bool $sent; private array $phoneNumbers; private Button $tlacitko; // ideálně $button private array $userAddresses;
-
✗ Špatně
this._Name = ""; this._send = false; this._phonenumbers = []; this._tlačítko = new Button(); this._uzivatelAddress = [];
✔ Správně
this._name = ""; this._sent = false; this._phoneNumbers = []; this._tlacitko = new Button(); // ideálně _button this._userAddresses = [];
Pojmenování metod a parametrů
Metody označují děje, jejich název obsahuje tedy sloveso! Může se jednat o:
- přikazovací tvar (
load()
,setId()
) - Metoda převážně provádí nějakou činnost a její výsledek může nebo nemusí vracet. Nevolíme infinitiv nebo -ing tvar, např.loading()
. - tázací tvar - Metodou se převážně ptáme na nějakou
hodnotu, než abychom chtěli provést nějakou akci (např.
getRank()
neboisDisabled()
pro hodnotu typuboolean
). Již víme, že takovým metodám říkáme gettery. - V případě více slov je ve většině jazyků pojmenováváme podle
camelCase
, v Pythonu podlesnake_case
. V C# začínají metody velkým písmenem.
Metody pojmenováváme podle toho, co dělají! Vyhýbáme se neurčitým
názvům jako work()
, action()
, run()
apod.
Ukažme si příklady:
-
✗ Špatně
public void printing(Employee employee) { public boolean getEnabled() { private void data() { public void work() {
✔ Správně
public void printInfo(Employee employee) { public boolean isEnabled() { private void generateData() { public void importBackup() {
-
✗ Špatně
public void Printing(Employee employee) public bool GetEnabled() private void Data() public void Work()
✔ Správně
public void PrintInfo(Employee employee) public bool IsEnabled() private void GenerateData() public void ImportBackup()
-
✗ Špatně
private: void data() { public: void printing(Employee employee) { bool getEnabled() { void work() {
✔ Správně
private: void generateData() { public: void printInfo(Employee employee) { bool isEnabled() { void importBackup() {
-
✗ Špatně
def printing(employee): def get_enabled(): def _data(): def work():
✔ Správně
def print_info(employee): def is_enabled(): def _generate_data(): def import_backup():
-
✗ Špatně
func printing(employee: Employee) { func getEnabled() -> Bool { private func data() { func work() {
✔ Správně
func printInfo(employee: Employee) { func isEnabled() -> Bool { private func generateData() { func importBackup() {
-
✗ Špatně
public function printing(Employee $employee): void { public function getEnabled(): bool { private function data(): void { public function work(): void {
✔ Správně
public function printInfo(Employee $employee): void { public function isEnabled(): bool { private function generateData(): void { public function importBackup(): void {
-
✗ Špatně
printing(employee) { getEnabled() { _data() { work() {
✔ Správně
printInfo(employee) { isEnabled() { _generateData() { importBackup() {
Parametry metod
Parametr metody je proměnná a proto pro jejich název
platí ta samá pravidla, jako pro proměnné a atributy
(například nikdy nepojmenujeme parametr param
🙂). Je tu ovšem
jedna důležitá věc na uvážení a to je použití dvojí
negace. Ukažme si poslední příklad:
-
✗ Špatně
public void importData(boolean disableForeignKeys) { importData(false);
✔ Správně
public void importData(boolean enableForeignKeys) { importData(true);
-
✗ Špatně
public void ImportData(bool disableForeignKeys = false)
✔ Správně
public void ImportData(bool enableForeignKeys = true)
-
✗ Špatně
public: void importData(bool disableForeignKeys = false) {
✔ Správně
public: void importData(bool enableForeignKeys = true) {
-
✗ Špatně
def import_data(disable_foreign_keys = False):
✔ Správně
def import_data(enable_foreign_keys = True):
-
✗ Špatně
func importData(disableForeignKeys: Bool = false) {
✔ Správně
func importData(enableForeignKeys: Bool = true) {
-
✗ Špatně
public function importData(bool $disableForeignKeys = false): void {
✔ Správně
public function importData(bool $enableForeignKeys = true): void {
-
✗ Špatně
importData(disableForeignKeys = false) {
✔ Správně
importData(enableForeignKeys = true) {
Kdo z vás na první dobrou dokáže říci, jestli jsou v první variantě
bez uvedení parametru (pro Javu s předáním false
) cizí klíče
povoleny? Vše je zas o lidském mozku, který není zvyklý fungovat pod dvojí
negací. Volíme tedy spíše druhou variantu.
V příští lekci, Nejčastější chyby programátorů - Podmínky, si ukážeme nejčastější chyby
začátečníků, například ohledně pojmenování kolekcí,
boolean
výrazů a DRY.