Лекция на 04 Април 2005 г. Праволинеен код Сравнително лесна задача Качество, правилност, четимост и поддръжка



Дата18.09.2016
Размер171.32 Kb.
#10106
ТипЛекция

mihail.stoynov Page 9/18/2016

Качествен програмен код


(изборен курс към ФМИ на СУ, летен семестър, 2004/2005 г.)
http://www.devbg.org/codecourse/

Бележки по 4.Организиране на праволинеен код

(Part IV STATEMENTS)

Pages 347-460

Михаил Стойнов, сряда, 04-07 Април 2005 г.
4.1 Организиране на праволинеен код

(14.Straight Line Code)


Лекция на 04 Април 2005 г.

Праволинеен код



  • Сравнително лесна задача

  • Качество, правилност, четимост и поддръжка

ДЕЛЕНИЕ

Последователността от оператори има значение



  • пример: open file, read data, close file

  • основното: има зависимости м/у операторите

  • пример(зависими - логика) вземи заплата, плати сметки

  • пример(не е ясно) – плати първо телефон, после ток

  • насоки:

  • Организирай кода така, че зависимостите да са ясни

  • Променете имената и логиката на методите, за да е ясно

  • Използвайте параметри на методи, за да става ясно

  • Документирайте неяснотите //

  • Проверявайте за зависимости с assert() или error-handling код: isTelephonePaid() – усложнява кода

Последователността от оператори няма значение

  • подреждането е много важно за четимостта, поддръжката, performance (производителност)

  • Принцип на близостта (Principle of proximity): дръж заедно общите оператори

  • Кода трябва да се чете от горе надолу

  • Пример: отвори, отвори, чети, чети, затвори, затвори

  • Групиране: може ли кода да се сложи в кутийки (Може и вложено), Ако не може – проблем

  • Ако отделните кутийки нямат общо – сложи в различни методи !

4.2 Използване на условни конструкции за управление



(15.Using Conditionals)
Условна конструкция – която контролира изпълнението на оператор. while и for се разглеждат в отделна глава.
IF оператор

  • в повечето езици има основните видове if

  • най-простите: if-then

  • по-сложни: if-then-else

  • най-сложни if-then-else-if

IF-THEN

  • напишете първо нормалното развитие на кода, после изключителните случаи

  • проверете дали при равенство отивате в правилната посока (> вместо >=) off-by-one error

  • нормалното развитие в if, вместо в else

  • пример: alabala; if error else alabala2; if error else…

  • пренапиши примера правилно

  • стека от грешки накрая е пример за добре написан код

  • нека след if има смислена клауза: не if () ; else …

  • поправи го – с отрицание

  • провери няма ли нужда от else – General Motors изследване – 50-80% имало нужда от else

  • едно решение е да има else без оператори, само с коментар защо няма оператори – за да е ясно

  • пример с горното if parameter is valid (онова за цветовете, в else пише защо няма код

  • не прекалявайте с горното

  • при тестване тествайте и else клаузата

  • проверете да не сте обърнали if-а и else-а !

Вериги: IF-THEN-ELSE-IF

  • примера с character, който ставаше isPunctuation,isControl

  • оправи примера с boolean function calls

  • сложи най-често срещаните първо

  • проверете дали всички случаи са покрити – сложете една последна else клауза със съобщение, че там не трябва да се стига

  • променете веригите if-then-else със други конструкции ако езикът ви ги поддържа (case)

  • примерът със VB:


Select Case inputChar

Case “a” To “Z”

Alabala;

Case “?”, “!”, “(”….

Alabala2;

Case Else

Alabala;

End Select
Case Оператор

  • case или switch е много различна при различните езици

  • при java case-а е само за числа

  • при VB – обхвати (като в примера), изброяване – много добро

Организиране на случаите в case

  • ако са малко на брой няма значение

  • ако са много, много ясно че подредбата има значение

  • ако са еднакво важни – азбучно или по номера

  • ако има един нормален, и много изключения – много ясно, че нормалния ще е първи

  • добра подредба е по честота на срещанията – бързина, четимост

Полезни съвети за case

  • действията във всеки case да са прости – използвайте методи, ако са сложни действия

  • не използвайте измислени променливи само за да можете да използвате case

  • примера с първата буква на командата Copy (c),Delete (d)

  • оправи примера с if-then-else

  • не използвайте default за последния случай, направете го с case, default използвайте само за случай по подразбиране,

  • защото грешките или непознати случаи ще отиват при default

  • използвайте default за грешки

  • в C++ и Java не забравяйте break;

  • лош пример:

case ‘a’: if (test){ operator1;

case ‘b’ operator…

}

break;




  • влагането на оператори е достатъчно трудно да се разбере, а тук – по-сложно от мозъчна хирургия

  • модификациите тук – mission impossible

  • по принцип не прескачайте отвъд един case (use break;) – предразполага към грешки, трудно се модифицира

  • но ако все пак ще го правите, сложете коментар, че нарочно това искате

Накрая: не всички конструкции са равни – използвайте най-подходящата за конкретния случай
4.3 Работа с конструкции за цикъл

(16. Controlling Loops)


Цикъл е най-общо конструкция, която служи за повторение на един и същ блок код.

for, while, do-while в C++, Java

For-Next, While-Wend, Do-Loop-While във VB

Циклите са сложни, трябва да можете да ги ползвате


Избиране на подходящия цикъл

  • counted loop цикълът със точен брой итерации (for) се изпълнява предварително точно зададен брой пъти (за всеки студент)

  • continuously evaluated loop цикъл, който се оценява на всяка итерация – не знае колко точно пъти ще се изпълни. Оценява ситуацията на всяка итерация (докато свърши файла, до достигане на грешка, докато потребителят се откаже)

  • endless loop безкраен цикъл – изпълнява се до безкрай, pacemakers, микровълнови печки

  • iterator loop цикъл с итератор (foreach) – изпълнява се точно веднъж за всеки елемент на дадена колекция (масив).

Делят се на:



  • дали един масив е flexible – дали проверява условието си на всяка итерация. (while - flexible, for – not flexible)

  • кога проверяват дали условието е изпълнено – в началото, средата или края.

  • разбираме дали цикълът се изпълнява поне веднъж

Кога да изберем while цикъл



  • новаците си мислят, че един while се прекратява в момента, в който условието стане грешно, без значение на кой оператор сме – не е вярно

  • много гъвкав избор

  • използвайте го, когато не знаете колко пъти искате да извършите дадена операция.

  • дали условието е изпълнено се проверява веднъж на итерация

  • най-важното е да се прецени дали да се проверява в началото или в края

  • ПИТАНЕ В НАЧАЛОТО

  • Може да се направи стандартно в повечето езици, да се емулира в останалите

  • ПИТАНЕ В КРАЯ

  • изпълнява се поне веднъж

  • Може да се направи стандартно в повечето езици, да се емулира в останалите

Кога да изберем цикъл с проверка някъде из тялото



  • съдържа начало, тяло (+ проверка за изход), край

  • пример за такъв цикъл

Do…



If (…) Then Exit Do



Loop


  • пример за цикъл и половина (слагане на space м/у всяка буква)

  • това е лошо, защото промените могат да изтърват операторите извън цикъла

  • поправи примера

  • така при промени няма да има проблеми

  • може да се добавят коментари за яснота

  • проучване през 1983 показва, че този тип цикъл е по ясен на студенти отколкото с проверка в началото или края

Нестандартен цикъл, който започва от средата



goto middle;

while(…)
{


//направи нещо друго  FIX ME

middle:

//направи нещо

}


  • лошо заради goto и заради неяснотата

  • може да се обърне и да се използва break;

Кога да използваме for



  • когато в началото знаем колко пъти трябва да изциклим

  • използвайте го само за прости неща

  • хубавото е, че го настройвате в началото и в цикъла не пипате нищо – black box

  • ако има случай, в който трябва да излезете от цикъла преждевременно не използвайте for, a while

Кога да използваме foreach



  • foreach in C#, For-Each in VB, for-in in Python

  • подходящ за операции с масиви, контейнери, колекции

  • елиминира housekeeping arithmetic, а така и шансовете за грешки

Контролиране на цикли
Какво може да се оплеска?

  • грешно или пропуснато инициализиране на цикъла

  • изпусната инициализация на акумулатори или други променливи

  • неправилно влагане

  • неправилно прекратяване

  • пропуснато увеличаване на променлива или неправилно увеличаване

  • индексиране на масив с неправилните променливи или по неправилен начин.

Решения


  • минимизирайте факторите, които влияят на цикъла

  • Опростявай! Опростявай! Опростявай! Опростявай!

  • дръжте управлението колкото се може повече извън цикъла

  • да бъде ясно без да се гледа в тялото при какви условия се изпълнява

  • третирайте тялото на цикъла като черна кутия, ако можете да разберете при какви условия се изпълнява, значи всичко е ок

  • пример с черна кутия

Вход в цикъла



  • влизайте в цикъла само от 1 място

  • инициализационният код да е близо до цикъла (Principle of proximity)

  • стават проблеми ако не е така – при промяна или преместване в по-голям цикъл

  • използвайте while( true ) за безкрайни цикли

  • event loop – който върти събития

  • for i = 1 to 99999 - лошо

  • for( ;; ) също става, макар че аз лично не го харесвам

  • предпочитайте for цикли ако са подходящи – всичко на едно място

  • не използвайте for ако while е по-подходящ

Лекция на 07 апреля 2005 г.




  • ако все пак искате да използвате for вместо while и имате следния пример


// read all the records from a file

for( inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++ )

{

inputFile.GetRecord();

}
става
// read all the records from a file

for( inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); inputFile.GetRecord() )

{

recordCount++;

}

Все пак трябва да е:


// read all the records from a file

inputFile.MoveToStart();

recordCount = 0;

while( !inputFile.EndOfFile() )

{

inputFile.GetRecord();

recordCount++;

}
Тялото на цикъла

  • използвайте скоби винаги (добра defensive programming практика) – не удрят performance или size

  • избягвайте празни цикли

  • пример за празен цикъл


while( (inputChar = dataFile.GetChar()) != CharType_Eof )

{

;

}
става:
do

{

inputChar = dataFile.GetChar();

}

while( inputChar != CharType_Eof );


  • дръжте housekeeping arithmetic или в началото или в края ако не може на някакво специално място

  • в тази връзка променливите, които инициализирате точно преди цикъла, ще са housekeeping arithmetic

  • нека всеки цикъл прави едно нещо. Това, че може да прави две неща не значи, че трябва да ги прави. Ако с един цикъл става по-бързо, сложете коментар: тия двата цикъла като ги обединиш става по-бързо. Чак като ви кажат оптимизирай, щото програмата върви бавно, чак тогава ги обединете.

Изход от цикъл



  • бъдете сигурни, че цикъла свършва

  • направете условията за изход от цикъла очевидни – слагайте ги на едно място

  • не си играйте с индексатора на for цикъл

  • избягвайте код, който разчита на последната стойност на индексната променлива

  • пример:


for( int i=0; i

{

if( *found )

break;

}

if( i

return true;

else

return false;
оправи кода със булева променлива


  • можете да използвате safety counters


int safetyCounter = 0;

do

{

...

safetyCounter++;

if( safetyCounter>= SAFETY_LIMIT )

Assert( false, “Safety Counter Violation” );

}...


  • не прекалявайте с тях – усложняват кода

Излизане от цикъл преждевременно




  • какво е break, Exit Do, Exit For, continue

  • continue е съкращение на if-then клауза

  • използвайте break вместо булеви флагове за изход от while цикъл – намаляват индентацията, по-ясен код

  • пазете се от много break оператори разпръснати из кода

  • примера с телефоните и канарчето в мината

  • използвайте continue за цикли с условие в началото

while( not eof(file) ) do

read( record, file )

if( record.Type <> targetType ) then

continue

... ...

end while

  • не го използвайте ако if няма да е в началото на тялото

  • използвайте break с етикет ако това е възможно

do {

...

switch(...)

EXIT_IF:

if(){

...

break EXIT_IF;

...

}...


  • много внимателно използвайте break и continue

Проверка на крайните състояния



  • три интересни места: начало, междинно състояние, край

  • минете ги наум

  • проверете за off-by-one error

  • добрите програмисти минават тези три състояния на ум. Даже проверяват сметките с калкулатор. Неефективните програмисти тестват докато тръгне правилно

  • но те не знаят дали е тръгнало правилно или ако е тръгнало защо е тръгнало

  • неефективните програмисти нагласяват нещата

  • <  <=, +-1

  • симулациите на ум носят: по-малко грешки при кодирането, по-бързо намиране на грешки при дебъгване, по-добро разбиране на кода, много добра дисциплина.

Използване на loop променливи (loop variables) - индексатори



  • използвайте целочислени или енумерирани типове за цикли и масиви, никога реални числа

  • използвайте добри имена на индексаторите


for( int i=0; i

for( int j=0; j<12; j++ ) {

for( int k=0; k

sum = sum + transaction[j][i][k];

}

}

}
става:
for( int iPayCode=0; iPayCode

for( int month=0; month<12; month++ ) {

for( int iDivision=0; iDivision

sum = sum + transaction[month][iPayCode][iDivision];

}

}

}


  • грешката с i,j,i, вместо i,j,k

  • ограничете видимостта на индекс променливата само до цикъла

  • в Ada индексаторите са невалидни извън for цикъла.

  • твърди се, че можете да използвате два цикъла един след друг с едно и също име на индексатора

  • не го праветев различните компилатори върви по различен начин

Колко дълъг трябва да е един цикъл



  • нека се вижда на цял екран

  • ограничете вложеността до три нива

  • преместете тялото на дълги цикли в методи

  • не използвайте рискови конструкции в цикли с дълго тяло

Как лесно да направим цикъл – отвътре навън



  • премисли как да го направиш

Връзка между цикли и масиви



  • пример за събиране на матрици със APL


for( int row=0; row

for( int col=0; col

product[row][col] = a[row][col]+b[row][col];

}

}
APL: product <- a + b


  • така че използвайте най-подходящия подход

Най-важното за цикли



  • дръжте ги прости, защото са сложни структури

  • прости: минимизирайте влагането, ясни входове и изходи, housekeeping code на едно място

  • без екзотични типове цикли

  • индексаторите с добри имена, не ги използвайте за повече от едно нещо

  • минете на ум целия цикъл, за да огледате всички възможни варианти

4.4 Необичайни конструкции за управление

(17.Unusual Control Structures)


  • има конструкции, които още не са се утвърдили като полезни или вредни

  • не присъстват във всички езици

  • ако се използват с внимание, могат да донесат ползи

Няколко точки за изход от метод (multiple returns from routine)



  • return, exit, Exit Sub, Exit Function,

  • използвайте подобни структури, когато повишават четимостта


Comparison Compare( int value1, int Value2 ) {

if( value1 < value2 ) {

return Comparison_LessThan;

}

else if( value1 > value2 ) {

return Comparison_GreaterThan;

}

Return Comparison_Equal;

}

  • използвайте guard clauses, за да опростите сложни конструкции за предпазване ог грешки


Comparison Compare( int value1, int Value2 )

if( file.ValidName() ) {

if( file.Open() ) {

if( file.hasValidStructure() ) {

// code

}

}

}
става: още една стъпка между двете (по-краткия вариант)
if( file.ValidName() ) {

// error handling

}

if( file.Open() ) {

// error handling

}
if( file.hasValidStructure() ) {

// error handling

}
// code


  • последния пример не е най-доброто, което може да се направи

  • намалете до минимум използването на return

Рекурсия


  • при рекурсията метода решава малка част от проблема, разбира остатъка на малки парчета и извиква сам себе си за остатъка от проблемите

  • пример: quick sort:


void quickSort( int firstiIndex, int lastIndex, String[] names )

if( lastIndex > firstIndex ) {

int midPoint = partition( firstIndex, lastIndex, names );

quickSort( firstIndex, midPoint-1, names );

quickSort( midPoint+1, lastIndex, names );

}

}


  • за някои неща рекурсията може да създаде прости, елегантни решения

  • за друга голяма група рекурсията може да създаде кратки, елегантни, трудни за разбиране решения

  • примера с лабиринта

Tips


  • бъдете сигурни, че рекурсията някога свършва

  • използвайте предпазни броячи, за да бъдете сигурни, че рекурсията привършва

  • пример

  • ограничете рекурсията до един метод

  • наглеждайте стека – предпазните броячи да са съобразени с това колко памет сте готови да заделите

  • ако във всяко извикване заделяте нов обект с много памет, по-добре го заделяйте в heap-а

  • не използвайте рекурсията за факториел или за числата на Фибоначи

  • пример как факториел с рекурсия става итерация

  • изводи

  • 1.компютърните книги понякога не ви правят услуга, като ви дават тъпи примери за рекурсия - факториел

  • 2. рекурсията е много мощен инструмент

  • 3. проучете всички други варианти преди да използвате рекурсия, всеки рекурсивен алгоритъм може да се замени с итеративен използвайки стекове. В различните случаи подходите са различни


goto

  • диспута за goto не е умрял – погледнете sourceforge.net

  • диспута се е променил малко – за multiple returns, error processing, exception handling

Аргументите против goto



  • започва се с писмо на Едгар Дийкстра „Go To Statement Considered Harmful” март 1968 Communications of the ACM

  • той наблюдава, че качеството на кода е обратно пропорционално на броя на използването на goto.

  • по-късно той твърди, че код без goto може по-лесно да се докаже, че работи правилно

  • код с goto по-трудно се форматира – indentation. Щото goto влияе на логиката, а логическото групиране дефинира индентацията.

  • използването на goto понякога пречи на компилатора да оптимизира кода, защото не може да проследи какво става

  • практиката показва, че използването на goto нарушава принципа, че кода трябва да е от горе надолу – да се чете така.

  • дори ако goto се използва правилно, употребата му се разпространява като термити и се прекалява.

  • опита показва, че използването на goto води до лош код.

  • java няма goto

В защита на goto



  • ако се използва много внимателно, може и да има ползи от него

  • всичко започва от Fortran, в който не е имало конструкция за цикъл и това е налагало използването на goto

  • тогава се е пишел много спагети код

  • обаче понякога goto може да помогне да се избегне повтаряне на код, тогава риска от използване на код е по-малък от този на грешки от непълни корекции на повтарящия се код

  • Кнут, който е написал книга против goto, дава и примери, в които има полза от goto

  • доброто програмиране не значи елиминиране на goto. В повечето случаи goto е излишно. Постигането на код без goto не е цел, а резултат. Самоцелното писане на код без goto е вредно.

  • след десетилетия проучвания, не е доказано, че goto е зло.

  • има опровержения на подобни опити

  • на последно място goto го има в повечето от модерните езици – C++, VB, Ada – за който се твърди, че е най-внимателно конструирания език за програмиране в историята.

Измисления дебат за goto



  • в повечето случаи примерите за и против са глупави нагласени и не дават реален код

Ето и няколко примера, в които goto може да се използва



  • споделяне на код в else



if( test )

{

if( other_test )

{

variable = ...;

goto MID_ELSE;

}

}

else

{

variable = ...;

MID_ELSE:
//lots of code;

}

  • решението с отделяне в метод с параметър

  • с няколко проверки – по-трудно се чете

Заключение



  • за някои хора е въпрос на религия (аз например)

  • има един на 100 случая, в които goto може да помогне

  • Мислете! Мислете! Мислете!

  • внимавайте, опишете всичко и използвайте

  • винаги бъдете отворени за други подходи предложени от други хора, може пък точно това решение да не сте го видели

    • използвайте goto за емулиране на конструкции (цикли главно) в езици, в които ги няма стандартно не се отклонявайте спазвайте конструкциите стриктно

    • не използвайте goto ако такава конструкция вече има

    • в повече случаи, в които е важна производителността, кода може да се пренапише – по-четим и с никаква или незначителна загуба на производителност

      • ако не е такъв случая – документирай!

    • ограничете се до едно goto за метод, освен ако не емулирате конструкция

    • не ходете назад, освен ако не емулирате конструкция

    • проверете дали всички етикети се използват, ако не проверете за грешки и ги махнете

    • ако сте мениджър не се бийте за едно goto, не си струва битката, просто проверете дали се използва правилно


By R. Lawrence Clark*

From DATAMATION, December, 1973

10 J=1


11 COME FROM 20

12 WRITE (6,40) J STOP

13 COME FROM 10

20 J=J+2


40 FORMAT (14)

- като най-важен извод, не се уповавайте на предразсъдъци, а мислете, мислете, мислете преди да направите нещо.

4.5 Методи, базирани на таблици

(18.Table-Driven Methods)




  • методи, базирани на таблици е конструкция, която позволява да се търси информация в таблица, вместо работа с логически конструкции като if и case

  • двата подхода са общо взето взаимозаменяеми

  • в простите случаи естествено логическият подход е за предпочитане

  • но когато веригата от if-ове нарасне, таблиците стават все по-примамливи

  • използван правилно, метода с таблиците е по-прост, по-лесно променим и/или по-ефективен

  • примера с char map, пунктуационен знак, буква, цифра, контрол

  • оправя се с енумерация, и един масив[256] от тип тази енумерация

Две важни неща при използването на методи, базиране на таблици



  • първо: как да търсим в таблицата

    • директно – примера с месеците

    • индиректно – примера с ЕГН

      • директен достъп

      • индексен достъп

      • стълбовиден достъп

  • второ: какви данни да има в таблицата

    • понякога има данни

    • понякога има действия (указатели към методи, делегати…) – по сложно става

Директен достъп



  • примера със месеците (+ високосна година, как се решава )

  • примера със insurance rates (male/female, age, marital status, smoking)

  • как да го оправим – най-логичното – да сложим годините в масив

  • после правим многомерен масив със всичко

  • как да сложим данните, може да ги hardcode-нем, можем да ги прочетем от всякъде

Какво става ако в таблицата се налага да има много дублираща се информация



  • първи подход: ами дублираме информацията

  • 2: променяме ключа, където това е възможно (онова между 17 и 66 години)

    • Изолирайте бърникането в ключа в отделен метод

Индексиран достъп



  • дай пример: 100 стоки и номер на стоката четирицифрено число – нарича се индексираща таблица

  • другото е, че лесно можете да правите много индексиращи таблици

  • има по-лесна поддръжка

Стъпаловиден достъп

mihail.stoynov Page 9/18/2016



Сподели с приятели:




©obuch.info 2024
отнасят до администрацията

    Начална страница