Když pomineme informaci o typu proměnné (té se budeme věnovat později), může nutnost explicitní deklarace proměnných vzbuzovat dojem přílišné byrokracie. Často tomu tak dokonce je. Na druhou stranu ovšem i tento aspekt souvisí s potřebou úrovně organizace odpovídající náročnosti projektu.
Explicitní deklarace dokumentuje úmysl, vytváří místo, ke kterému
je možné vztáhnout dokumentaci, ale také už během překladu umožňuje
odhalit chyby vznikající v důsledku nekonzistentního pojmenovávání
proměnných, např. při použití proměnných acctNo
a
acctNum
. Navíc také naprosto jasně vymezuje rozsah
platnosti proměnné.
Implicitní deklarace zjednodušuje psaní a omezuje byrokracii zvláště při psaní běžného kódu, ale také může způsobit řadu problémů, právě proto, že nedokumentuje úmysl a hranice platnosti proměnné nejsou na první pohled zřejmé.
Na explicitní deklaraci pravděpodobně není až takový problém nutnost explicitně proměnnou zavést, potažmo nutnost proměnnou deklarovat na začátku funkce – tohle už v dnešních jazycích není až takový problém. Byrokracie spočívá hlavně v nutnosti specifikovat typ proměnné v situacích, kdy by ho mohl správně "domyslet" překladač.
V případě statického typování je typ svázán s proměnnou a při přiřazování hodnot je kontrolována typová kompatibilita, což je dobrá vlastnost. Některé implicitní typové konverze mohou situaci trochu zamlžovat, ale obecně je striktní typová kontrola žádoucí. Znalost typů během překladu navíc umožňuje překladači generovat efektivnější kód.
V případě dynamického typování je typ svázán s hodnotou a proměnná je jenom pojmenované místo, které může hodnotu obsahovat. Typ proměnné je odvozen od hodnoty, kterou obsahuje a za běhu programu se může měnit.
Přestože se dynamické typování v řadě případů hodí např. pro rychlé prototypování, nebo práci s databázemi, pro bězné programování poskytuje až příliš mnoho volnosti a pro rozsáhlejší projekty je nutné dodržovat striktní disciplínu.
Vlastně se ukazuje, že dynamické typování slouží často jako berlička v situacích, kterým by mnohem více slušela typová inference. Pokud při psaní kód potřebujeme několik proměnných pro mezivýsledky a pomocné výpočty, explicitní deklarace typu obtěžuje a navíc duplikuje typovou informaci. Dynamické typování tuto byrokracii odstraní, ale odstraní i striktní typovou kontrolu, což je něco co nechceme.
Při použití typové inference by prostě překladač odvodil typy proměnných od typů atributů nebo návratových hodnot funkcí vystupujících ve výrazech, takže by nebylo nutné typ deklarovat explicitně, to vše při zachování typové kontroly.
var
typu pro lokální proměnnévar count = 5;
var message = "Hello";
var array = new [] { 0, 1, 2 };
var list = new List <int> ();
for
a foreach
cyklech
for (var i = 1; i < 10; i++) { ... }
foreach (var item in list) {... }
using
using (var file = new StreamReader ("C:\\myfile.txt")) { ... }
Nesprávně inicializovaná data představují jednu z nejčastějších příčin chyb.
Chyby vzniklé v důsledku špatné inicializace mají tendenci projevovat se nejrůznějšími způsoby, což činí jejich hledání obtížné. Navíc se mohou objevit po změně v kódu, která s místem chyby vůbec nesouvisí.
Zvláštní lahůdkou v této kategorii jsou ukazatele. S jejich inicializací typicky souvisí také alokace paměti. Neinicializovaný ukazatel se dá typicky snadno odhalit u statických proměnných (tam bude automaticky inicializován na 0, tedy NULL), ale u proměnných alokovaných na zásobníku nebo heapu tento luxus není.
Věci, které spolu souvisí, mají být v kódu u sebe.
Hlavní důvod proč deklarovat a inicializovat proměnné blízko místa použití je "Principle of Proximity", tj. související věci mají být pohromadě. To pak např. umožňuje vzít část kódu a přesunout ho jinam, aniž by se zapomnělo na inicializaci proměnné.
// declare all variables int accountIndex; double total; boolean done; // initialize all variables accountIndex = 0; total = 0.0; done = false; ... // code using accountIndex ... // code using total ... // code using done while (!done) { ...
// declare all variables int accountIndex; double total; boolean done; // initialize all variables accountIndex = 0; total = 0.0; done = false; ... // code using accountIndex ... // code using total ... // code using done while (!done) { ...
int accountIndex = 0; // code using accountIndex ... double total = 0.0; // code using total ... boolean done = false; / code using done while (!done) { ...
Inicializace proměnných na jednom místě vytváří dojem, že se všemi
proměnnými se pracuje po celou dobu. Přitom např. proměnná
done
se používá až na konci. Než se začne vykonávat kód,
který proměnnou done
používá, mohla být proměnná
(chybně) změněna. Navíc při změně programu (už jen kvůli ladění) může
dojít k přidání cyklů kolem kódu, který proměnnou done
používá a ta pak bude správně inicializována pouze jednou.
Princip lokality, který jsme použili u inicializace se dá použít obecně. V případě proměnných se snažíme mimo jiné omezit viditelnost jednotlivých proměnných tak, aby bylo jasné, které věci spolu souvisí a mají tedy zůstat pohromadě.
Minimalizací metrik VS a VLT se obecně zmenšuje se prostor pro chyby při práci s proměnnými. V důsledku toho se kód stává jaksi planárnějším a tedy čitelnějším a lépe pochopitelným – související části kódu se vejdou na obrazovku a není potřeba ho při změnách nebo ladění držet v hlavě.
1 // initialize all variables 2 recordIndex = 0; 3 total = 0; 4 done = false; ... 26 while (recordIndex < recordCount) { 27 ... 28 recordIndex = recordIndex + 1; ... 64 while (!done) { ... 69 if (total > projectedTotal) { 70 done = true;
1 // initialize all variables 2 recordIndex = 0; 3 total = 0; 4 done = false; ... 26 while (recordIndex < recordCount) { 27 ... 28 recordIndex = recordIndex + 1; ... 64 while (!done) { ... 69 if (total > projectedTotal) { 70 done = true;
25 recordIndex = 0; 26 while (recordIndex < recordCount) { 27 ... 28 recordIndex = recordIndex + 1; ... 62 total = 0; 63 done = false; 64 while (!done) { ... 69 if (total > projectedTotal) { 70 done = true;
recordIndex ... 4
total ... 8
done ... 8
7
25 recordIndex = 0; 26 while (recordIndex < recordCount) { 27 ... 28 recordIndex = recordIndex + 1; ... 62 total = 0; 63 done = false; 64 while (!done) { ... 69 if (total > projectedTotal) { 70 done = true;
Existuje nějaké číslo, které striktně oddělí špatný kód od dobrého? Pevná čísla nejsou známa, ale je dobře se snažit je minimalizovat.
Mimochodem, zkuste aplikovat uvedený postup na globální proměnné. Jaké byste očekávali výsledky?
Tato doporučení jsou v důsledkem minimalizace průměrných hodnot veličin variable span a variable live time.
Při seskupování souvisejících příkazů je často lepší je abstrahovat do procedury/funkce.
Postup při stanovení nutného rozsahu platnosti proměnné se postupuje podobně jako při konfiguraci zabezpečení. Začne se s ničím resp. maxinálně restriktivním nastavením a toto se na potřebných místech uvolňuje po nejmenších možných krocích tak, aby systém dělal co chceme.
void SummarizeData (...) {
...
GetOldData (oldData, &numOldData);
GetNewData (newData, &numNewData);
totalOldData = Sum (oldData, numOldData);
totalNewData = Sum (newData, numNewData);
PrintOldDataSummary (oldData, totalOldData, numOldData);
PrintNewDataSummary (newData, totalNewData, numNewData);
SaveOldDataSummary (totalOldData, numOldData);
SaveNewDataSummary (totalNewData, numNewData);
...
}
Přestože tento kód na první pohled nevypadá špatně, vyžaduje po čtenáři aby držel v hlavě šest proměnných. Jak tento pohled podporují dříve zmíněné metriky?
void SummarizeData (...) {
...
GetOldData (oldData, &numOldData);
totalOldData = Sum (oldData, numOldData);
PrintOldDataSummary (oldData, totalOldData, numOldData);
SaveOldDataSummary (totalOldData, numOldData);
...
GetNewData (newData, &numNewData);
totalNewData = Sum (newData, numNewData);
PrintNewDataSummary (newData, totalNewData, numNewData);
SaveNewDataSummary (totalNewData, numNewData);
...
}
Tento kód vyžaduje po čtenáři aby držel v hlavě pouze tři proměnné v daném okamžiku. Navíc z kódu viditelně vystupuje princip práce, který je kandidátem pro abstrakci.
Čím menší je stavový prostor programu menší, tím lépe.
Šetřte čas čtenáře/upravujícího na úkor pisatele.
Na stavový prostor programu můžeme z několika směrů. Z hlediska stavu výpočtu je stavový prostor programu definován všemi možnými hodnotami všech proměnných. Stejně ho vnímají např. verifikační nástroje. Omezováním platnosti proměnných vytváříme jakési podprostory, na které je možné pohlížet jako na černou skříňku.
Z pohledu programátora (a rovněž překladače) se dá na stavový prostor pohlížet jako na množinu věcí (funkcí, proměnných, ...) platných v každém bodě programu. Programátorovi minimalizace tohoto počtu usnadňuje pochopení programu, překladači zase umožňuje generovat lepší kód.
Minimalizace rozsahu platnosti vyžaduje úsilí na straně pisatele (místo toho aby byly všechny proměnné globální a snadno přístupné), ale výsledkem je nejenom program, který je pro čtenáře lépe pochopitelný, ale celkově lépe navržený program.
-1
= nekonečnoČasté je využívání dolních bitů ukazatelů, protože ty dnes bývají zarovnané na 4 bajty.
Hodí se to například u interpretů jazyků, kde jsou potřeba rychlé operace s celými čísly. Podle dolních bitů se rozhodne, zda se v horní části daných 4 bajtů nachází číslo, nebo ukazatel na nějaký složitější objekt. V případě čísla se tak ušetří jedna dereference (za cenu, že takové číslo nemůže zabrat celých 32 bitů).
Pokud jste se někdy divili, proč má JavaScript nebo Ruby o jeden bit menší celá čísla, než byste čekali, teď už víte proč.
V konečném důsledku je jak přetěžování účelu nebo významu proměnné špatné proto, že název proměnné je buď příliš obecný a nic neříkající, což je špatně, nebo se hodí pouze pro jeden typ použití a jeden význam, což je (pokud bychom přetěžování připustili) také špatně.
Nejlepší je vnímat proměnné jako názvy pro koncepty, bez nutné spojitosti s místem v paměti, kde má být proměnná uložena. To má na starosti překladač, který může (a často to dělá) usoudit, že proměnná žádné místo v paměti nepotřebuje.
// compute roots of a quadratic equation
temp = sqrt (b^2 - 4 * a * c);
root [0] = (-b + temp) / (2 * a);
root [1] = (-b - temp) / (2 * a);
// swap the roots
temp = root [0];
root [0] = root [1];
root [1] = temp;
// compute roots of a quadratic equation
temp = sqrt (b^2 - 4 * a * c);
root [0] = (-b + temp) / (2 * a);
root [1] = (-b - temp) / (2 * a);
// swap the roots
temp = root [0];
root [0] = root [1];
root [1] = temp;
// compute roots of a quadratic equation
discriminant = sqrt (b^2 - 4 * a * c);
root [0] = (-b + discriminant) / (2 * a);
root [1] = (-b - discriminant) / (2 * a);
// swap the roots
oldRoot = root [0];
root [0] = root [1];
root [1] = oldRoot;
Příklad s víceúčelovou pomocnou proměnnou obsahuje vazbu mezi blokem kódu pro výpočet a blokem kódu pro prohození kořenů. Při abstrakci těchto bloků do funkcí je nutné ještě znovu deklarovat pomocnou proměnnou. Navíc při modifikaci kódu může dojít ke změně typu pomocné proměnné, přičemž nový datový typ už se pro oba účely hodit nemusí.
Použití samostatné proměnné pro každý účel tyto problémy eliminuje. Navíc je možné tyto proměnné deklarovat a inicializovat blíže místu použití, což zvyšuje čitelnost a pochopitelnost kódu.
David Majda: Zažil jsem programátora, kterého při programování
systému pro správu elektronického obchodu nenapadlo definovat
konstantu pro DPH. Několik měsíců nato se sazba DPH změnila z 22% na
19%. Co bylo horší, toho člověka ani nenapadlo použít alespoň pro
převrácenou hodnotu výraz 1 / 1.22
a pro jistotu všude
psal 0.819672
.
if (characterType & 0x03) ...
if (reportType == 16) ...
if (fileName.length() < 255) ...
if (characterType & 0x03) ...
if (reportType == 16) ...
if (fileName.length() < 255) ...
final int CHAR_TYPE_LETTER = 0x01;
final int CHAR_TYPE_DIGIT = 0x02;
final int CHAR_TYPE_ALPHANUMERIC = (CHAR_TYPE_LETTER | CHAR_TYPE_DIGIT);
final int MAX_FILENAME_LENGTH = 255;
enum ReportType {
DAILY, WEEKLY, MONTHLY;
}
if (characterType & CHAR_TYPE_ALPHANUMERIC) ...
if (reportType == ReportType.DAILY) ...
if (fileName.length() < MAX_FILENAME_LENGTH) ...
0 | Inicializace součtů, akumulátorů, indexů při for-cyklu |
---|---|
1 |
Inicializace součinů, indexů při for-cyklu, posuny o jedničku,
odečítání od konce
|
2 | Půlení intervalů, průměry |
Každé porušení principu lokality znamená, že programátor bude muset pobíhat po zdrojovém kódu sem a tam, aby vyhledal všechny potřebné informace ke kusu kódu, na kterém právě pracuje. To narušuje koncentraci a zdržuje, a tedy snižuje produktivitu.
Dnes situaci vylepšují "chytrá" IDE, kde se po najetí nad nějaký objekt zobrazí jeho definice nebo se na ni dá rychle odskočit a vrátit se zpět.
"You can't give a variable a name the way you give a dog a name
– because it's cute or it has a good sound."
– Steve
McConnell
x = x - xx;
xxx = aretha + SalesTax (aretha);
x = x + LateFee (x1, x) + xxx;
x = x + Interest (x1, x);
x = x - xx;
xxx = aretha + SalesTax (aretha);
x = x + LateFee (x1, x) + xxx;
x = x + Interest (x1, x);
balance = balance - lastPayment;
monthlyTotal = newPurchases + SalesTax (newPurchases);
balance = balance + LateFee (customerId, balance) + monthlyTotal;
balance = balance + Interest (customerId, balance);
Název proměnné by měl přesně a úplně popisovat, co proměnná reprezentuje.
numberOfPeopleOnTheUsOlympicTeam
numberOfSeatsInTheStadium
maximumNumberOfPointsInModernOlympics
x
, temp
, nebo i
jsou málo informativní a většinou
špatné
Rozumná výchozí technika pro vytváření názvů proměnných je prostě slovy říct, co konkrétní proměnná představuje. Někdy je to to nejlepší jméno, protože se dobře čte a neobsahuje zkratky a mnohoznačnosti. Težko se s něčím splete a dá se dobře zapamatovat, protože jméno je podobné konceptu, který označuje.
inputRecord
vs. employeeRecord
readyFlag
vs. printerReady
numberOfPeopleOnTheUsOlympicTeam
numberOfSeatsInTheStadium
n
, np
, ntm
n
, ns
, nsisd
numTeamMembers
, teamMemberCount
numSeatsInStadium
, seatCount
Najít optimální délku názvu proměnné je jako v té pohádce o chytré horákyni. Název nemůže být příliš dlouhý, protože se hůře píše a zakrývá vizuální strukturu kódu. Nemůže být ani příliš krátký, protože pak nedává velký smysl.
Jména jsou často tvořena podstatnými jmény, někdy s prefixem ve formě přídavných jmen. Jméno je pak možné krátit vypouštěním méně důležitých modifikátorů.
numSales
, saleNum
vs.
salesCount
, salesIndex
i
, j
, k
float frubbish = 0.0;
for (int i = 0; i < foo.length; i++) {
for (int j = 0; j < bar.length; j++) {
for (int k = 0; k < zap.length; k++) {
frubbish += frubbishDelta (foo[i], bar[k], zap[j]);
}
}
}
float frubbish = 0.0;
for (int i = 0; i < foo.length; i++) {
for (int j = 0; j < bar.length; j++) {
for (int k = 0; k < zap.length; k++) {
frubbish += frubbishDelta (foo[i], bar[k], zap[j]);
}
}
}
float frubbish = 0.0;
for (int fooIndex = 0; fooIndex < foo.length; fooIndex++) {
for (int barIndex = 0; barIndex < bar.length; barIndex++) {
for (int zapIndex = 0; zapIndex < zap.length; zapIndex++) {
frubbish += frubbishDelta (
foo[fooIndex], bar[barIndex], zap[zapIndex]);
}
}
}
temp = sqrt (b^2 - 4 * a * c);
root [0] = (-b + temp) / (2 * a);
root [1] = (-b - temp) / (2 * a);
discriminant = sqrt (b^2 - 4 * a * c);
root [0] = (-b + discriminant) / (2 * a);
root [1] = (-b - discriminant) / (2 * a);
status
vs. statusOK
sourceFile
vs. sourceFileAvailable
isStatus
pak zjevně nedávají smyslif (! notFound) ...
vs. if (found) ...
status
nehodí,
zde je tomu naopak
ALL_CAPS_SEPARATED_BY_UNDERSCORES
THREAD_TIME_SLICE
, THREAD_STACK_SIZE
CHAR_TYPE_DIGIT
, CHAR_TYPE_LETTER
, CHAR_TYPE_PRINTABLE
input
a inputValue
,
fileNumber
a fileIndex
file1
,
file2
vs.
srcFile
,
destFile
K názvům s podobným významem: analogie z algebry – maximální vs. největší prvek
if ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) ||
(elementIndex == lastElementIndex)) { ...
if ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) ||
(elementIndex == lastElementIndex)) { ...
finished = (elementIndex < 0) || (MAX_ELEMENTS < elementIndex);
repeatedEntry = (elementIndex == lastElementIndex);
if (finished || repeatedEntry) { ...
if (reportType == 1)
vs. if (reportType == ReportType.TERSE)
public enum Status { SUCCESS, WARNING, FAILURE }
for (LogLevel eachLevel : LogLevel.values()) { ...
*_FIRST
a *_LAST
#define LL_INFO 0
#define LL_WARNING 1
#define LL_ERROR 2
*_FIRST
a *_LAST
#define LL_INFO 0
#define LL_WARNING 1
#define LL_ERROR 2
#define LOG_LEVEL_FIRST 0
#define LOG_LEVEL_INFO 0
#define LOG_LEVEL_WARNING 1
#define LOG_LEVEL_ERROR 2
#define LOG_LEVEL_LAST 2
Doba života
recordIndex ... 27
total ... 67
done ... 67
Průměrná doba
54