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, kterých se začátečníci 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ívejme 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 pěti 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ů se měsíčně počítají obvykle v milionech korun, jeden den nefunkčnosti e-shopu tedy stojí majitele stovky tisíc Kč. Dodavateli pak 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 a opět se pohybujeme v řádech milionů Kč.
- Netestovatelné, nezabezpečené a takto bychom mohli pokračovat.
Není tedy pochyb o tom, že dobré praktiky jsou pro vývoj softwaru v týmu naprosto zásadní a následky jejich porušení 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 lékaři 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 lékaři trvá poměrně dlouho, než z pacienta dostane,
že ho píchá v patě, a proto si nemůže nazout ani botu. Když lékař
konečně vypustí pacienta z ordinace, zjistí, že pacient byl součástí
skupiny a venku čeká ještě několik desítek podobně nemocných.
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
dlouho trvá, než zjistí, že se jedná o třídu importující data z
externí zálohy a že se nejprve musí ručně zavolat metoda
work()
, která import provede. Když programátor chybu konečně
opraví, objeví se další chyba. Programátor následně zjistí, že v
programu je několik desítek tříd a metod, z jejichž názvu nelze vůbec
poznat, co dělají.
Určitě vidíme paralelu mezi těmito dvěma příběhy. V případě lidského těla by nás asi těžko napadlo hovořit o orgánu "přesouvat" a funkci "bota". V programech však bohužel není takový problém narazit na objekty pojmenované jako děje a funkce pojmenované jako věci, přestože princip je úplně stejný. Není tedy 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řídkakdy tvoříme celou třídu pouze na vypsání něčeho). - Začínají velkým písmenem – Každé další slovo má
velké písmeno (notace
PascalCase
, a to i v Pythonu). Je tak patrné, že jde o obecnou třídu, a ne o její konkrétní instanci. - Jsou pojmenované podle toho, kterou 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, v němž se třída jmenuje
WorkingWithFile
, jeho autor si pravděpodobně pouze vygooglil, že
kód se píše do class
, aniž 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);
Mnohem větší smysl by spíše dávalo, kdyby tyto metody měla na sobě
přímo třída Array
a kdybychom tedy psali:
employees.sort()
Autoři Javy však pravděpodobně nechtěli třídu Array
příliš složitou, a tak pro některé metody vytvořili onu 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 na ITnetwork používají česká pojmenování, aby byly lépe stravitelné. Kódy těch pokročilých jsou však již stejně jako reálné obchodní aplikace pojmenovávány anglicky. Pro anglické názvy tříd platí samozřejmě totéž, co jsme řekli výše. Můžeme však 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 všakCarManager
, 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ě tytéž 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 nikdy 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 tedy obsahuje sloveso! Může se jednat o:
- Rozkazovací tvar (
load()
,setId()
) – Metoda převážně provádí nějakou činnost a svůj výsledek může nebo nemusí vracet. Nevolíme infinitiv nebo -ing tvar, např.loading()
. - Tázací tvar – Metodou se spíše ptáme na nějakou
hodnotu, než že bychom 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# metody začínají 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í tatáž pravidla jako pro proměnné a atributy
(parametr například nikdy nepojmenujeme param
🙂). Je tu ovšem
jedna důležitá věc k uvážení, a to 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 dokáže na první pohled říct, jestli jsou v první variantě
bez uvedení parametru (pro Javu s předáním false
) cizí klíče
povoleny? Vše je opět záležitostí lidského 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.