Steve McConnell o strukturovaném programování:
V jádru strukturovaného programování je jednoduchá myšlenka, že program by měl používat pouze řídící konstrukce s jedním vstupem (single-entry) a jedním výstupem (single-exit). Taková konstrukce představuje blok kódu, do kterého se vstupuje pouze v jednom místě, a ze kterého se také v jednom místě vystupuje.
Strukturovaný program postupuje vpřed uspořádaným a disciplinovaným způsobem, narozdíl od nekontrolovaného skákání tam a zpátky. Takový program se dá číst od shora dolů a v podstatě stejným způsobem je i vykonáván. To umožňuje lépe chápat co program dělá, zvyšuje to čitelnost a v důsledku vede ke kvalitnějšímu programu.
Základní tezí strukturovaného programování je, že řízení toku libovolného výpočtu je možné dosáhnout kombinací základních primitiv: sekvence, výběru a iterace. Přestože dnes existuje celá řada jazykových obratů pro zvýšení pohodlnosti, většiny pokroku v programování bylo dosaženo omezováním toho, co je programátorům povoleno.
Proto je nutné použití jiných než základních primitiv, které často porušují princip single-entry/single-exit, vnímat velmi kriticky a být schopen použití takových primitiv zdůvodnit.
... to je prostě posloupnost příkazů ...
... to je prostě posloupnost příkazů ...
... na tom přece není co zkazit ...
data = ReadData ();
results = CalculateResultsFromData (data);
PrintResults (results);
revenue.ComputeMonthly ();
revenue.ComputeQuarterly ();
revenue.ComputeAnnual ();
ComputeMarketingExpense ()
ComputeSalesExpense ()
ComputeTravelExpense ()
ComputePersonnelExpense ()
DisplayExpenseSummary ()
data = ReadData ();
results = CalculateResultsFromData (data);
PrintResults (results);
revenue.ComputeMonthly ();
revenue.ComputeQuarterly ();
revenue.ComputeAnnual ();
ComputeMarketingExpense ()
ComputeSalesExpense ()
ComputeTravelExpense ()
ComputePersonnelExpense ()
DisplayExpenseSummary ()
ComputeMarketingExpense()
také
inicializuje data, do kterých ostatní procedury ukládají své
výsledky, které nakonec použije DisplayExpenseSummary()
?
InitializeExpenseData (expenseData)
ComputeMarketingExpense (expenseData)
ComputeSalesExpense (expenseData)
ComputeTravelExpense (expenseData)
ComputePersonnelExpense (expenseData)
DisplayExpenseSummary (expenseData)
ComputeMarketingExpense (marketingData)
ComputeSalesExpense (salesData)
ComputeTravelExpense (travelData)
ComputePersonnelExpense (personnelData)
DisplayExpenseSummary (marketingData, salesData, travelData, personnelData)
Expense marketingExpense = ComputeMarketingExpense (marketingData)
Expense salesExpense = ComputeSalesExpense (salesData)
Expense travelExpense = ComputeTravelExpense (travelData)
Expense personnelExpense = ComputePersonnelExpense (personnelData)
DisplayExpenseSummary (
marketingExpense, salesExpense, travelExpense, personnelExpense)
...
if (log.isDebugEnabled ()) {
log.debug (...);
}
...
Item item = itemCache.get (key);
if (item == null) {
item = createItem (key);
itemCache.put (key, item);
}
...
...
if (log.isDebugEnabled ()) {
log.debug (...);
}
...
Item item = itemCache.get (key);
if (item == null) {
item = createItem (key);
itemCache.put (key, item);
}
...
void setSamplePeriod (long samplePeriod) {
if (samplePeriod > 0) {
this.samplePeriod = samplePeriod;
}
}
if (snapshot.sampleCount != 0) {
snapshot.average = runningTotal / snapshot.sampleCount;
} else {
snapshot.average = 0;
}
probe = probeFactory.create (probeKind);
if (probe != null) {
registerProbe (probe);
} else {
log.error (..., probeKind);
}
if (snapshot.sampleCount != 0) {
snapshot.average = runningTotal / snapshot.sampleCount;
} else {
snapshot.average = 0;
}
probe = probeFactory.create (probeKind);
if (probe != null) {
registerProbe (probe);
} else {
log.error (..., probeKind);
}
probe = probeFactory.create (probeKind);
if (probe == null) {
log.error (..., probeKind);
} else {
registerProbe (probe);
}
...
if () {
...
if () {
...
}
}
...
...
if () {
...
if () {
...
} else {
...
}
} else {
...
}
...
OpenFile (inputFile, status)
If (status = Status_Error) Then
errorType = ErrorType_FileOpenError
Else
ReadFile (inputFile, fileData, status)
If (status = Status_Success) Then
SummarizeFileData (fileData, summaryData, status)
If (status = Status_Error) Then
errorType = ErrorType_DataSummaryError
Else
PrintSummary (summaryData)
SaveSummaryData (summaryData, status)
If (status = Status_Error) Then
errorType = ErrorType_SummarySaveError
Else
UpdateAllAccounts ()
EraseUndoFile ()
errorType = ErrorType_None
End If
End If
Else
errorType = ErrorType_FileReadError
End If
End If
OpenFile (inputFile, status)
If (status = Status_Success) Then
ReadFile (inputFile, fileData, status)
If (status = Status_Success) Then
SummarizeFileData (fileData, summaryData, status)
If (status = Status_Success) Then
PrintSummary (summaryData)
SaveSummaryData (summaryData, status)
If (status = Status_Success) Then
UpdateAllAccounts ()
EraseUndoFile ()
errorType = ErrorType_None
Else
errorType = ErrorType_SummarySaveError
End If
Else
errorType = ErrorType_DataSummaryError
End If
Else
errorType = ErrorType_FileReadError
End If
Else
errorType = ErrorType_FileOpenError
End If
switch
/case
/given
===
,
neomezené množství hodnot
switch
switch (inputVar) {
case 'A': if (test) {
// statement 1
// statement 2
case 'B': // statement 3
// statement 4
}
break;
}
switch
switch
je blokcase
jsou jen návěští
break
break
se zapomínábreak
okomentovatswitch (errorDocumentationLevel) {
case DocumentationLevel.FULL:
displayErrorDetails (errorNumber);
// Fall through: FULL level also prints summary
case DocumentationLevel.SUMMARY:
displayErrorSummary (errorNumber);
// Fall through: SUMMARY level also prints error number
case DocumentationLevel.NUMBER_ONLY:
displayErrorNumber (errorNumber);
break;
default:
throw new AssertionError ("Invalid documentation level.");
}
action = userCommand [0];
switch (action) {
case 'c':
Copy ();
break;
case 'd':
DeleteCharacter ();
break;
case 'f':
Format ();
break;
case 'h':
Help ();
break;
...
default:
HandleUserInputError (ErrorType.InvalidUserCommand);
}
default
případudefault
default
případdefault
"nemůže nastat", vložit assertion
default
případuswitch (transaction.type) {
case TransactionType.DEPOSIT:
ProcessDeposit (transaction);
break;
case TransactionType.WITHDRAWAL:
ProcessWithdrawal (transaction);
break;
case TransactionType.TRANSFER:
ProcessTransfer (transaction);
break;
case TransactionType.NOOP:
// no processing required
break;
default:
LogTransactionError ("Unknown transaction type", transaction);
}
break
else
jako defaultif (userCommand.equals (COMMAND_STRING_COPY)) {
Copy ();
} else if (userCommand.equals (COMMAND_STRING_DELETE)) {
Delete ();
} else if (userCommand.equals (COMMAND_STRING_FORMAT)) {
Format ();
} else if (userCommand.equals (COMMAND_STRING_HELP)) {
Help ();
} else {
HandleUserInputError (ErrorType.InvalidCommandInput);
}
int compare (int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}
int compare (int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
}
return 0;
}
int compare (int a, int b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
sub compare ($$) {
given ($1) {
when ($_ < $2) {
return -1;
}
when ($_ > $2) {
return 1;
}
default {
return 0;
}
}
}
Klíčová slova v tuto chvíli nejsou podstatná i když je zjevné, že uvedené základní typy cyklů se typicky realizují pomocí konstrukcí for, for-each, while a do-while.
Nicméně velké množství cyklů spadá do kategorie s flexibilním počtem opakování, protože jsou v nich používány testy uprostřed těla cyklu, které umožňují buď předčasně ukončit provádění těla cyklu skokem na začátek cyklu, nebo předčasně ukončit celý cyklus skokem za něj.
while (!inputFile.EndOfFile () && moreDataAvailable) {
}
,
je tabu – použít while, pokud se for nehodíwhile (true) { ... }
vs.
for (;;) { ... }
// read all the entries from a file
for (logFile.MoveToStart (), entryCount = 0; !logFile.EndOfFile;
entryCount++) {
logFile.getEntry ();
}
// read all the entries from a file
for (logFile.MoveToStart (), entryCount = 0; !logFile.EndOfFile;
entryCount++) {
logFile.getEntry ();
}
entryCount = 0;
for (logFile.MoveToStart (); !logFile.EndOfFile(); logFile.getEntry()) {
entryCount++;
}
// read all the entries from a file
for (logFile.MoveToStart (), entryCount = 0; !logFile.EndOfFile;
entryCount++) {
logFile.getEntry ();
}
entryCount = 0;
for (logFile.MoveToStart (); !logFile.EndOfFile(); logFile.getEntry()) {
entryCount++;
}
entryCount = 0;
logFile.MoveToStart ();
while (!logFile.EndOfFile ()) {
logFile.GetEntry (&logEntry);
putEntry (entryCount, logEntry);
entryCount++;
}
V prvním případě příkazy týkající se proměnné entryCount
vzbuzují dojem, že tato proměnná se podílí na řízení cyklu, přestože
cyklus posouvá vpřed volání logFile.getEntry()
, které je
naopak v těle cyklu.
Druhý případ ukazuje v principu správné a logické použití příkazu for
,
jelikož v hlavě cyklu jsou pouze příkazy, které tento cyklus řídí, zatímco příkazy
týkající se proměnné entryCount
jsou umístěny před tělem (inicializace)
a v těle (aktualizace) cyklu.
S ohledem na potenciální nutnost testovat, zda se záznam povedlo přečíst,
je však v tomto případě nejvhodnější použití příkaz while
.
Příkaz for
by měl být primárně využíván pro cykly, jejichž
řízení je jednoduché a po přečtění hlavy cyklu není třeba se jím příliš
zabývat.
while ((inputChar = cin.get ()) != '\n') {
;
}
do {
inputChar = cin.get ();
} while (inputChar != '\n');
while ((inputChar = cin.get ()) != '\n') {
inputLine.append (inputChar);
}
while ((inputChar = cin.get ()) != '\n') {
inputLine.append (inputChar);
}
do {
inputChar = cin.get ();
if (inputChar == '\n') {
break;
}
inputLine.append (inputChar);
} while (true);
while ((inputChar = cin.get ()) != '\n') {
inputLine.append (inputChar);
}
do {
inputChar = cin.get ();
if (inputChar == '\n') {
break;
}
inputLine.append (inputChar);
} while (true);
inputChar = cin.get ();
while (inputChar != '\n') {
inputLine.append (inputChar);
inputChar = cin.get ();
}
for (recordCount = 0; recordCount < MAX_RECORDS; recordCount++) {
if (entry [recordCount] == testValue) {
break;
}
}
// lots of code
...
if (recordCount < MAX_RECORDS) {
return true;
} else {
return false;
}
found = false;
for (recordCount = 0; recordCount < MAX_RECORDS; recordCount++) {
if (entry [recordCount] == testValue) {
found = true;
break;
}
}
// lots of code
...
return found;
found = false;
for (recordCount = 0; recordCount < MAX_RECORDS; recordCount++) {
if (entry [recordCount] == testValue) {
found = true;
break;
}
}
// lots of code
...
return found;
Použití continue mi nepřijde zdaleka intruzivní jako break. Pokud pracujeme s konceptem těla cyklu jako funkce, continue představuje předčasný návrat z funkce, což je poměrně běžný jev a uživatele nemusí tolik zajímat – funkce prostě před zpracováním dat ověří, že je na nich co zpracovávat.
Oproti tomu break je jakousi analogií výjimky a jeho použití v těle cyklu se dá přirovnat k vyvolání výjimky ve funkci, jejíž volající výjimku neošetřuje a nechává ji projít až o úroveň výš, v našem případě tedy ven z cyklu cyklu. Proto break činí řízení cyklu složitějším a jeho použití je nutné umět zdůvodnit.
do {
...
switch (...) {
...
if (...) {
...
break;
...
}
...
}
...
} while (...);
do {
...
switch (...) {
...
if (...) {
...
break;
...
}
...
}
...
} while (...);
do {
...
switch (...) {
...
CALL_CENTER_DOWN:
if (...) {
...
break CALL_CENTER_DOWN;
...
}
...
}
...
} while (...);
Podle Software Engineering News (1990) byl break v uvedeném příkladu navíc (byl míněn pro vyskočení z if) a 15 ledna 1990 způsobil 9-hodinový výpadek telefonního systému v New York City.
U breaku s návěštím (což např. Java umožňuje) by bylo jasné, co je cílem. Nicméně uvedený kód se stejně Java překladači nebude líbit, protože pokud by za příkazem break byl nějaký další kód, překladač bude protestovat, že ten kód je nedosažitelný.
for (int i = 0; i < a.length; i++) {
System.out.println (a [i]);
}
for (String s: a) {
System.out.println (s);
}
int compare (int a, int b)
If file.validName () Then
If file.Open () Then
If encryptionKey.valid () Then
If file.Decrypt (encryptionKey) Then
' lots of code
...
End If
End If
End If
End If
If file.validName () Then
If file.Open () Then
If encryptionKey.valid () Then
If file.Decrypt (encryptionKey) Then
' lots of code
...
End If
End If
End If
End If
' set up, bailing out if errors are found
If Not file.validName () Then Exit Sub
If Not file.Open () Then Exit Sub
If Not encryptionKey.valid () Then Exit Sub
If Not file.Decrypt (encryptionKey) Then Exit Sub
' lots of code
...
' set up, bailing out if errors are found
If Not file.validName () Then
errorStatus = FileError.InvalidFileName
Exit Sub
End If
If Not file.Open () Then
errorStatus = FileError.CannotOpenFile
Exit Sub
End If
If Not encryptionKey.valid () Then
errorStatus = FileError.InvalidEncryptionKey
Exit Sub
End If
If Not file.Decrypt (encryptionKey) Then
errorStatus = FileError.CannotDecryptFile
Exit Sub
End If
' lots of code
...
process_t process_create (...) {
...
proc_thread = (struct thread *) kmalloc (sizeof (struct thread));
if (proc_thread == NULL)
goto fail_exit;
...
proc_vmm = vmm_alloc ();
if (proc_vmm == NULL)
goto fail_clean_thread;
...
result = vmm_vmalloc (proc_vmm, & program_image, ...)
if (result != EOK)
goto fail_clean_vmm;
...
proc_stack = vmm_alloc_stack (proc_vmm, ...);
if (proc_stack != NULL)
goto fail_clean_image;
...
result = copy_to_vm (proc_vmm, proc_image, ...);
if (result != EOK)
goto fail_clean_stack;
...
// now we can finally initialize the process
...
return (process_t) proc_thread;
// Here comes error handling
fail_clean_stack:
vmm_vfree (proc_vmm, proc_stack);
fail_clean_image:
vmm_vfree (proc_vmm, proc_image);
fail_clean_vmm:
vmm_free (proc_vmm);
fail_clean_thread:
kfree (thread);
fail_exit:
return NULL;
}
TRUE ~ (1 == 1), FALSE ~ !TRUE
if (document.atEndOfStream() && !inputError
&& lineCount >= MIN_LINES && lineCount <= MAX_LINES
&& !errorProcessing()) {
/* ... */
}
boolean allDataRead =
document.atEndOfStream() && !inputError;
boolean legalLineCount =
(lineCount >= MIN_LINES) && (lineCount <= MAX_LINES);
if (allDataRead && legalLineCount && !errorProcessing()) {
/* ... */
}
&&
a ||
na stejné úrovni
&&
a ||
určit výsledek z prvního operandu, druhý operand se nevyhodnotí.
&&
)null
if (s != null && s.length() > 0) { ... }
0
null
referencí a nulovou délkou řetězceif (slideIndex >= 0 && slideIndex < slides.length) { ... }
False
implicitní0
, '\0'
a NULL
explicitní0
má mnoho významůNULL
i prázdný řetězec/pole==
a =
{
a }
i když je v těle jen jeden
příkaz
{
a }
{
a }
odstraňovat