SlideShare a Scribd company logo
Качествен  програмен код Светлин Наков Национална академия по разработка на софтуер www.devbg.org
Лекторът Светлин Наков Директор на Национална академия по разработка на софтуер (НАРС) Безплатни курсове за програмисти –  Java  и  .NET Обучение по стипендия + осигурена работа Председател на Българска асоциация на разработчиците на софтуер (БАРС) Преподавател по съвременни софтуерни технологии в СУ "Св. Климент Охридски" Консултант по разработка на софтуер Носител на наградата "Джон Атанасов" на президента на България за 2004
Съдържание Дефиниция за качествен код Софтуерен дизайн Висококачествени подпрограми Защитно програмиране Правилно използване на променливите Имената на променливите Преработка на съществуващ код Самодокументиращ се код
Качествен програмен код Какво е качествен програмен код?
Какво е качествен код? Качеството на софтуера има 2 аспекта: Външно качество – видимото за потребителя Коректност на софтуера Удобство и леснота за работа Производителност (скорост на работа) Вътрешно качество – вътрешната организация на архитектурата и програмния код Разбираемост Леснота за промяна и добавяне на функционалност (поддръжка) Простота на реализацията
Какво е качествен код? Характеристики за качество на кода: Коректност Четимост и разбираемост Висока свързаност на отговорностите  (strong cohesion)  на всички нива (модули, класове, методи) Функционална независимост ( loose coupling)  на всички нива (модули, класове, методи) Добро, консистентно форматиране Подходящо и консистентно именуване на класовете, методите, променливите и останалите елементи Добра документация, вградена в кода
Качествен програмен код Какво е качествен софтуерен дизайн?
Софтуерен дизайн Качеството на софтуера силно зависи от качеството на дизайна Дизайнът е трудно-дефинируем процес Итеративен, недетерминистичен Няма точна рецепта как да се прави Основна цел на дизайна: Да се управлява сложността на софтуера Основен похват при дизайна: Функционална декомпозиция на проблемите на всички нива
Какво е софтуерен дизайн Софтуерният дизайн е: Принципна организация на софтуерната система Дизайнът се състои от: Архитектурен план Описва основните компоненти и подсистеми на системата и взаимодействието между тях Детайлен дизайн Описва вътрешната организация на отделните компоненти и подсистеми Описва класовете и методите в класовете
Характеристики на дизайна Минимална сложност, леснота за разбиране Леснота за поддръжка (промяна и разширяване) Функционална независимост между подсистемите, компонентите и класовете ( loose coupling ) Преизползваемост ( reusability ) Висок a  входна зависимост ( fan-in ) Някои  utility  класове   се използват много често Ниска изходна зависимост ( fan-out ) Един клас да не ползва прекалено много други класове Минималност – да няма излишни части
Процесът на дизайн Функционална декомпозиция: Разделяме системата на подсистеми Разделяме подсистемите на класове и ги подреждаме в пакети  ( пространства от имена) Разделяме класовете в подпрограми (методи) Проектираме подпрограмите (чрез псевдокод) Не е необходимо да се прави паралелно във всички посоки Започва се от най-важната функционалност за клиента
Фази на дизайна Разделяне на подсистемите на класове Идентификация на обектите и процесите от реалния свят Съпоставяне на класове за тези обекти Идентификация на връзките между обектите Проектиране на класова йерархия Използване на шаблони ( design patterns ) Скриване на възможно най-много имплементационни детайли
Фази на дизайна Разделяне на класовете на методи Идентификация на действията, които всеки обект може да извършва (методи) Идентификация на съществените характеристики на обектите (атрибути) Скриване на възможно най-много имплементационни детайли  (private  методи) Максимална функционална независимост  (loose coupling) Висока свързаност на отговорностите  (strong cohesion)
Фази на дизайна Проектиране на вътрешността на методите Най-често е отговорност на програмистите, а не на архитектите Подбор на подходящи алгоритми Описание на алгоритмите чрез псевдокод
Силата на диаграмите Извикване на уеб услуга Изпълнение на SQL заявка Резултат от SQL заявка XML DataSet Променени данни във вид на XML DataSet Заявки за нанасяне на промените Клиент Web-услуга База данни
Качествен програмен код Какво са качествените подпрограми (методи)?
Защо да използваме методи? Намаляваме сложността Разбиваме сложните проблеми на по-прости Добавяме междинни нива на абстракция Скриваме детайли за имплементацията Намаляваме риска от неуспех Избягваме повторението на еднакъв код Скриваме сложни последователности от действия Скриваме работата с указатели Опростяваме сложни булеви проверки
Свързаност на отговорностите Свързаност на отговорностите (strong cohesion) е ключово изискване за методите Генерален принцип: Един метод трябва да прави само едно нещо и да го прави добре Операциите в един метод трябва да са взаимосвързани – насочени към обща задача Идеалният случай: Функционална кохезия Методът извършва единична ясно дефинирана операция Пример: функция  S qrt()
Допустими видове кохезия Последователна кохезия Редица от стъпки за решаване на единна задача Пример:  SendEmail() свързваме се към сървъра за поща изпращаме съобщението затваряме връзката Комуникационна кохезия Свързаност на действията по общи данни Пример:  DisplayReport() извличаме данните форматираме ги отпечатваме ги
Допустими видове кохезия Времева кохезия Действия, които се извършват по едно и също време Пример:  LoadSettings() зареждаме настройките за шрифтовете зареждаме настройките за цветовете зареждаме настройките за принтера зареждаме настройките за базата данни зареждаме настройките за Интернет достъпа
Недопустими видове кохезия Логическа кохезия Изпълнява се различно действие според някой входен параметър (код на операция) Лош пример:  ReadAll(int op _code )  – прочита артикул, цена, адрес или ЕГН   според подадения код Изключение: Обработчици на събития (event handlers) Случайна кохезия (липса на свързаност) Няколко несвързани едно с друго действия Изключително лоша практика!
Имената на методите Името трябва да описва всичко, което методът извършва Ако няма подходящо име, имаме лоша кохезия ! Избягвайте безлични и общи думички Лош пример:  HandleStuff() ,  ProcessData() Не използвайте цифри в името Лош пример:  ReadProfile1() ,  ReadProfile2() Дължината на името трябва да е толкова дълга, колкото е необходимо (9-15 символа) Ако името е прекалено дълго, имаме лоша кохезия Използвайте английски език
Имената на методите Имената на функциите трябва да описват връщаната стойност Пример:  GetNumberOfProcessors() Имената на процедурите се съставят по схемата  <глагол> + <обект> Пример:  PrintReport() ,  LoadSettings() Използвайте консистентно противоположностите Пример:  OpenFile()  и  CloseFile() Лош пример:  OpenFile()  и  _descriptor_close() Използвайте конвенция за честите операции Пример:  GetName() ,  GetAge() ,  SetName() ,  SetAge() Спазвайте конвенцията навсякъде
Колко да са дълги методите? Предпочитайте кратки методи (до един екран) Методите трябва да имат силна кохезия Това е много по-важно от дължината им! Методите трябва да са дълги &quot;колкото трябва&quot; Не разделяйте на части даден метод само защото е много дълъг
Параметрите на методите Подреждайте параметрите в последователност  ( < входни > , <входно-изходни>,  < изходни > ) Подреждайте консистентно параметрите при методи с подобни параметри Използвайте всички параметри Ако връщате статус или код за грешка, сложете този параметър последен Не използвайте параметрите като работни променливи (не модифицирайте параметрите) Документирайте неочевидните допускания Например мерната единица при подаване на числа
Параметрите на методите Ограничете броя на параметрите до около 7 Човешкото съзнание не може да следи повече от 7 неща едновременно (знаехте ли това?) Разграничавайте входните от изходните параметри (ако езикът не го поддържа) Кога да подаваме обект и кога няколко негови полета? Съобразете се логически методът над какво работи – над обекти или над съвкупност от стойности Подавайте параметрите в коректния им ред Използвайте именувано извикване, ако се поддържа
Функция или процедура Функция или процедура? Използвайте функция когато основната задача на метода е да изчисли и върне някаква стойност Уверете се, че всеки път на изпълнение връща стойност Не връщайте указател към локални данни Запазете в променлива стойността преди да я върнете: return  days * hoursPerDay * ratePerHour; int salary = days * hoursPerDay * ratePerHour; return salary;
Качествен програмен код Какво е защитно програмиране?
Защитно програмиране Защитно програмиране ( defensive programming) Насочено към защита на кода от некоректни данни Пази кода от грешки, които никой не очаква Имплементира се чрез проверка на коректността на всички входни данни данните, идващи от външни източници входните параметри на методите Имплементира се чрез  assertions,  изключения и други средства за управление на грешки
Проверки ( assertions) Проверките ( assertions)  следят за различни очаквания за състоянието на програмата Улавят неочаквани входни параметри или вътрешни състояния Силно улесняват откриването на грешки в кода Представляват изрази от вида: assert( условие, съобщение _ за _ грешка) Ако условието е нарушено, програмата завършва аварийно и се отпечатва грешката При  release  компилация се премахват от кода Много са полезни при големи и сложни проекти
Проверки ( assertions) Проверките на практика &quot;документират&quot; скритите допускания, които кодът очаква Някои езици поддържат  assertions , в другите можем да си ги реализираме сами Типични грешки, улавяни с  assertions: Стойност  NULL  на входен параметър Стойност извън допустимия диапазон за входен параметър Невалидно състояние на файл, поток или друг манипулатор на ресурс Излизане извън размера на масив или колекция
Assertions –  препоръки Използвайте изключения или друг механизъм за контрол на очакваните грешки Използвайте assertions само за грешки, които никога не трябва да се случват Не слагайте изпълним код в assertion Лош пример:  assert(ConnectToDatabase(), &quot;Can not establish database connection!&quot;) Използвайте  assertions  за да документирате входни и изходни условия в методите Добавете код за управление на грешката след  assertion  (за по-голяма надеждност)
Изключения ( exceptions ) Изключенията ( exceptions ) предоставят мощен механизъм за централизирано управление на грешки и непредвидени ситуации Позволяват проблемните ситуации да се обработват на много нива Улесняват писането и поддръжката на надежден програмен код Изключенията могат да бъдат класове – да се наследяват и да образуват йерархии Могат да се използват на мястото на  assertions
Изключения – препоръки Използвайте изключения, за да уведомите другите части на кода за проблеми, които не трябва да бъдат игнорирани Хвърляйте изключение само в ситуации, които наистина са изключителни и трябва да се обработят по някакъв начин Ако даден проблем може да се обработи локално, направете го и не хвърляйте изключение Хвърляйте изключенията на подходящо ниво на абстракция Пример:  GetEmplyeeInfo()  може да хвърля  EmployeeException , но не и  FileNotFoundException
Изключения – препоръки Включвайте в съобщението на изключението пълно описание на причината за възникването му Всеки  catch  блок трябва да прихваща само изключенията, които очаква и знае как да обработва, а не всички Catch  блоковете трябва да са подредени така, че да започват от изключенията най-ниско в йерархията и да продължават с по-общите Избягвайте празни  catch  блокове Не е правилно да прихващате всички изключения, без да ви интересува типа им
Изключения – препоръки Очаквайте описаните в документацията изключения Документирайте изключенията, които вашият код може да предизвика Управлявайте всички необработени изключения централизирано Можете да покажете съобщение за проблем на потребителя и да запишете проблема в  log  файл Установете стандарти за изключенията в приложението и дефинирайте класова йерархия Хвърляйте само обекти от тип &quot;изключение&quot; или негови наследници, а не указатели и числа
Колко защитно програмиране да оставим в  Release  версията Оставете кода, който проверява за важни грешки Премахнете кода, който проверява за маловажни грешки Премахнете кода, който предизвиква непосредствени сривове Заместете го с код, който прекратява програмата &quot;културно&quot;, без загуба на данни Непременно  log- вайте грешките при клиента Ако показвате съобщения за проблеми на потребителя, съобразете се с неговите знания
Качествен програмен код Как да използваме променливите?
Принципи при инициализиране Проблемите: Неинициализирана променлива Пример:  int value; Частично инициализирана променлива Пример: Student student = new Student(); Student.Name = &quot;Бай Мангал&quot;; // Student.Age – не е инициализирано
Инициализирайте променливите в момента на деклариране  Инициализирайте всяка променлива близо до мястото където се използва за пръв път Обръщайте специално внимание на променливите за броене и натрупване Инициализирайте член-променливите на един клас в конструктора Принципи при инициализиране
Използвайте настройките на компилатора за автоматично инициализиране на променливите Включвайте предупредителните съобщения от компилатора Проверявайте входните параметри за валидност Проверявайте за невалидни указатели към паметта Инициализирайте работната памет в началото на програмата Принципи при инициализиране
Обхват, живот, активност Обхват ( variable scope)  – колко “известна” е една променлива Глобална (статична), член-променлива, локална Диапазон на активност  (span)  – среден брой линии между обръщенията към даден променлива Живот ( lifetime ) – обем на кода от първото до последното рефериране в даден метод Проследете къде се използва дадена променлива ,  нейният диапазон на активност и период на живот Направете обхвата, живота и активността на променливите колкото се може по-малки
Работа с променливи Инициализирайте променливите извън тялото на цикъла Не инициализирайте променлива до момента, в който ще бъде използвана Групирайте сходните операции Започнете с най-малкия обхват и разширявайте, ако се наложи Използвайте всяка променлива точно и само за една цел Избягвайте променливи със скрито значение Използвайте всички декларирани променливи
Качествен програмен код Почивка!
Качествен програмен код Как да именуваме променливите?
Именуване на променливи Избирайте добро име! Името трябва да описва точно и ясно обекта, който променливата представлява Добри имена:  account ,  blockSize ,  customerDiscount Лоши имена:  r18pq ,  __hip ,  rcfd ,  val1 ,  val2 Адресирайте проблема, който решава променливата – “какво” вместо “как” Добри имена:  employeeSalary ,  employees Лоши имена:  myArray ,  customerFile ,  customerHashTable
Именуване на променливи Оптимална дължина на името – 10 до 16 символа Изборът на име зависи от обхвата Променливите с по-голям обхват и по-дълъг живот имат по-дълго и описателно име: protected Account[] mCustomerAccounts; Променливите с малък обхват и кратък живот могат да са по-кратки: for (int i=0; i<customers.Length; i++) { … } Използвайте пространства за избягване на повторения при декларирането: System.Windows.Forms.TextBox System.Web.UI. Web Controls.TextBox
Именуване на специфични типове данни Именуване на броячи Пример:  UsersCount ,  RolesCount ,  FilesCount Именуване на променливи за състояние Пример:  ThreadState ,  TransactionState Именуване на временни променливи Пример:  index ,  value ,  count Лош пример:  a ,  aa ,  tmpvar1 ,  tmpvar2 При булеви променливи използвайте имена, които дават предпоставка за истина или лъжа Пример:  canRead ,  available ,  isOpen ,  valid
Именуване на специфични типове данни Булевите променливи трябва да носят &quot;истина&quot; в името си Пример:  isReady ,  canRead ,  hasMoreData Лош пример:  notReady ,  cannotRead ,  noMoreData Именуване на изброими типове Използвайте вградените изброени типове (когато езикът за програмиране ги поддържа): Color.Red ,  Color.Yellow ,  Color.Blue Или използвайте подходящи префикси: colorRed ,  colorBlue ,  colorYellow Именуване на константи – с главни букви Пример:  MAX_FORM_WIDTH ,  BUFFER_SIZE
Кога е необходима конвенция за именуване Когато екипът е по-голям   Когато програмата ще се поддържа дълго време Когато програмата ще се проверява от други програмисти във Вашата организация Когато програмата е прекалено голяма и е невъзможно да се проследят всички модули наведнъж Когато програмата ще спре да се развива за известно време Когато във вашия проект имате много непозната терминология, обща за целия проект
Стандартни префикси Унгарска конвенция – използва се все по-рядко Дефинирани типове от потребителя Например:  typedef int Color; Семантични префикси (напр.  btnSave ) Не изпускайте букви за да съкратите името Съкращавайте по един и същ начин из целия код Създавайте имена, които да можете да произнесете (не като  btnDfltSvRzlts ) Избягвайте комбинации, които водят до друга дума или различно значение  ( напр.  preFixStore )
Стандартни префикси Документирайте кратките имена в кода Помнете, че имената са предназначени за хората, които ще четат кода, а не за тези които го пишат Избягвайте заблуждаващи имена или съкращения Избягвайте променливи с подобни имена, но с различно предназначение Например:  UserStatus  и  User Current Status Избягвайте имена, които звучат еднакво Избягвайте цифри в имената  ( напр.  pi314 ) Избягвайте грешно написани думи в имената
Стандартни префикси Избягвайте думи, които често се грешат Избягвайте използването на повече от един народен език Избягвайте използването на стандартни типове и ключови думи в имената на променливите Не използвайте имена, които нямат нищо общо с това което променливите съдържат Избягвайте имена, които съдържат трудни за четене символи
Качествен програмен код Какво е преработка на кода ( Refactoring )?
Митове и реалност за процеса за разработка на софтуер Митът Когато един проект стриктно спазва правилата на процеса за разработка, единствената последвала промяна в кода е само в периода на поддръжка на софтуера ( software maintenance phase) Така генерирането на код е праволинейно без да се налага преработка
Митове и реалност за процеса за разработка на софтуер Реалността Кодът постоянно се променя Причината: променя се разбирането за проблемната област в хода развитие на проекта Всяка промяна в изискванията налага промени и в съществуващия код Дори в най-добре управляваните проекти
Преработка на кода ( Refactoring) Еволюцията на софтуера наподобява биологичната еволюция Някои промени са с благоприятен ефект, други не са Еволюцията на софтуера е неизбежна Еволюцията е възможност да се приближим към “съвършения” продукт
Преработка на кода ( Refactoring) Основно правило на еволюцията на софтуера: Еволюцията трябва да подобрява начина на реализация на даден проект Основният начин за реализиране на това правило: Преработката на кода
Кога даден код се нуждае от преработка ? Повторение на код При проблеми в дублициран код се налага да се правят модификации на няколко места Даден метод е прекалено обемист Даден цикъл е прекалено обемист или съдържа дълбоко ниво на влагане Даден клас изпълнява несвързани отговорности ( poor cohesion) Даден клас не предоставя добро ниво на абстракция
Кога даден код се нуждае от преработка ? (продължение) Даден метод има дълъг списък с параметри Една промяна налага паралелна модификация на няколко класа Свързани една с друга данни се използват винаги заедно, но не са обединени в клас Даден метод използва повече функционалност от други класове отколкото от собствения си Даден клас е прекалено обвързан с друг Полета на даден клас са  public
Преработка на код на ниво данни Заместете “вълшебните” числа и низове с именувана константа (напр.  1024      BUF_SIZE ) Преименувайте дадена променлива с по-ясно и по-информативно име ( p      currentPos ) Преработете даден условен израз в метод Използвайте междинни променливи за резултата от сложни изрази Преобразувайте обикновени данни в нов клас Групирайте свързаните константи в изброими типове (enumerations)
Преработка на кода на ниво метод Преметете фрагмент от кода на даден метод в нов метод  (extract method) Премахнете даден метод, ако кодът, който съдържа, е прекалено прост и кратък Преработете дълъг и сложен метод в няколко по-малки или в изцяло нов клас Премахнете неизползваните параметри Ако има нужда от допълнителен параметър за даден метод, добавете го
Преработка на кода на ниво клас Променете обекти, подавани по стойност, с обекти, подавани по указател (референция) Изнесете общата функционалност за набор от класове в отделен базов клас Преместете метод от един клас в друг, ако той логически принадлежи на последния Преобразувайте един клас в два или повече Премахнете даден клас, ако не се ползва
Преработка на кода на ниво система Създайте абстракция на данните, върху които нямате контрол Дефинирайте клас, който ще енкапсулира тези данни и чиито обекти ще бъдат подавани на потребителите Ако не е наложително, премахвайте цикличните зависимости между класовете Ако не е нужна употребата на изключения, използвайте кодове за грешки Използвайте &quot; factory  метод&quot; за създаване на инстанции на даден клас според даден параметър
Качествен програмен код Какво е самодокументиращ се код и как се реализира?
Стилът на програмиране и документацията Документация на високо ниво Архитектурен план на системата Документация на ниско ниво Разглежда особености в най-големи детайли, касаещи кода на програмата Коментарите в кода не са основният източник на документация Добрият стил на програмиране е най-добрата документация! Самодокументиращ се код Лесно се разбира основната му цел
Характеристики на самодокументиращия се код Добра структура на програмата – подравняване, организация на кода Използване на ясни и лесни за разбиране конструкции Употреба на подходящи имена на променливи, методи и класове Употреба на именувани константи, вместо “магически” константи и стрингове Минимизация на сложността на реализацията
Самодокументиращ се код – важни въпроси Дава ли интерфейсът на класа добра абстракция? Подходящо ли е името на класа и показва ли основната му цел? Става ли ясно от интерфейса как трябва да се използва класа? Показва ли името на метода основната му цел? Всеки метод реализира ли една добре определена задача? Имената на променливите съответстват ли на тяхната употреба?
Самодокументиращ се код – важни въпроси (продължение) Групирани ли са свързаните един с друг оператори? Само една задача ли изпълняват конструкциите за итерация (циклите)? Има ли дълбоко влагане на условни клаузи? Показва ли организацията на кода неговата логическата структура? Дизайнът недвусмислен и ясен ли е? Скрити ли са детайлите на имплементацията възможно най-много?
“ Ефективни” коментари  Коментарите понякога могат да навредят повече отколкото да помогнат Добрите коментари не повтарят кода и не го обясняват – те изясняват неговата идея Коментарите трябва да обясняват на по-високо ниво какво се опитваме да постигнем Писането на коментари помага да осмислим по-добре това, което искаме да реализираме
Правила на “ефективните” коментари Използвайте псевдокод, когато е възможно Пишете коментари когато създавате самия код, а не след това Продуктивността не е добра причина за да не пишете коментари Документирайте всичко, което не става ясно от вашия код Поставянето на много коментари е толкова вредно колкото и липсата на такива Не коментирайте трудно разбираем код – по добре го преработете
Ресурси по темата Code Complete, 2 nd  edition, Steve McConnell, Microsoft Press, 2004, ISBN  0735619670 ,  http://www.cc2e.com/ Курс по &quot;Качествен програмен код&quot;   в СУ –   http://www.devbg.org/codecourse/
Качествен програмен код Въпроси?

More Related Content

PDF
Курс - Качество на софтуера - част 1
PDF
Тестове на уеб приложения
PPT
Видове софтуерни тестове
PDF
Тестове за ползваемост - Usability testing
PPTX
Професия QA инженер - SoftUniConf June 2015
PPTX
причини за автоматизация на тестването
PPT
Svetlin Nakov - Configuration Management
PPT
Svetlin Nakov - Mobile Code Security
Курс - Качество на софтуера - част 1
Тестове на уеб приложения
Видове софтуерни тестове
Тестове за ползваемост - Usability testing
Професия QA инженер - SoftUniConf June 2015
причини за автоматизация на тестването
Svetlin Nakov - Configuration Management
Svetlin Nakov - Mobile Code Security

Similar to High Quality Code Introduction (20)

PPT
Continuous integration (d.atanasov)
PPT
Soft eng.
PPTX
Курс по уеб програмиране (2014), занятие №3 - JavaScript (част 1/2)
PPTX
Курс по уеб програмиране (2015), занятие №3 - JavaScript (част 1/2)
PPTX
Курс по уеб програмиране (2015), занятие №1 - HTML
PPT
Managing Construction
PPT
FABRIQ - Short - Svetlin Nakov
PPT
Средства на VSTS за управление на проекти, версии на системата, извеждане на ...
PPT
FABRIQ - Presentation Nakov 0.8
DOCX
Programirane i organizaciq
PPT
Svetlin Nakov - E-Business And NASD Academy
DOCX
Курс по програмиране за напреднали (2012) - 4. Desktop приложения. Windows Forms
PPTX
Курс по информационни технологии (2013) - 3. ADO.NET, LINQ to SQL
PPT
Svetlin Nakov - .NET Framework Overview
PPT
Nakov - .NET Framework Overview + Security
PPTX
Курс по програмиране на C# 2013 - 1. Въведение в компютърното програмиране и C#
ODP
Enterprise Content Management with Nuxeo EP 5.3.0 (in bulgarian)
PPTX
Linux: Relaxing Administration - New Horizons Bulgaria
PPTX
Училищен курс по програмиране на C# (2013/2014), занятие №13
Continuous integration (d.atanasov)
Soft eng.
Курс по уеб програмиране (2014), занятие №3 - JavaScript (част 1/2)
Курс по уеб програмиране (2015), занятие №3 - JavaScript (част 1/2)
Курс по уеб програмиране (2015), занятие №1 - HTML
Managing Construction
FABRIQ - Short - Svetlin Nakov
Средства на VSTS за управление на проекти, версии на системата, извеждане на ...
FABRIQ - Presentation Nakov 0.8
Programirane i organizaciq
Svetlin Nakov - E-Business And NASD Academy
Курс по програмиране за напреднали (2012) - 4. Desktop приложения. Windows Forms
Курс по информационни технологии (2013) - 3. ADO.NET, LINQ to SQL
Svetlin Nakov - .NET Framework Overview
Nakov - .NET Framework Overview + Security
Курс по програмиране на C# 2013 - 1. Въведение в компютърното програмиране и C#
Enterprise Content Management with Nuxeo EP 5.3.0 (in bulgarian)
Linux: Relaxing Administration - New Horizons Bulgaria
Училищен курс по програмиране на C# (2013/2014), занятие №13
Ad

More from Svetlin Nakov (20)

PPTX
AI and the Future of Devs: Nakov @ Techniverse (Nov 2024)
PPTX
AI за ежедневието - Наков @ Techniverse (Nov 2024)
PPTX
AI инструменти за бизнеса - Наков - Nov 2024
PPTX
AI Adoption in Business - Nakov at Forbes HR Forum - Sept 2024
PPTX
Software Engineers in the AI Era - Sept 2024
PPTX
Най-търсените направления в ИТ сферата за 2024
PPTX
BG-IT-Edu: отворено учебно съдържание за ИТ учители
PPTX
Programming World in 2024
PDF
AI Tools for Business and Startups
PPTX
AI Tools for Scientists - Nakov (Oct 2023)
PPTX
AI Tools for Entrepreneurs
PPTX
Bulgarian Tech Industry - Nakov at Dev.BG All in One Conference 2023
PPTX
AI Tools for Business and Personal Life
PDF
Дипломна работа: учебно съдържание по ООП - Светлин Наков
PPTX
Дипломна работа: учебно съдържание по ООП
PPTX
Свободно ИТ учебно съдържание за учители по програмиране и ИТ
PPTX
AI and the Professions of the Future
PPTX
Programming Languages Trends for 2023
PPTX
IT Professions and How to Become a Developer
PPTX
GitHub Actions (Nakov at RuseConf, Sept 2022)
AI and the Future of Devs: Nakov @ Techniverse (Nov 2024)
AI за ежедневието - Наков @ Techniverse (Nov 2024)
AI инструменти за бизнеса - Наков - Nov 2024
AI Adoption in Business - Nakov at Forbes HR Forum - Sept 2024
Software Engineers in the AI Era - Sept 2024
Най-търсените направления в ИТ сферата за 2024
BG-IT-Edu: отворено учебно съдържание за ИТ учители
Programming World in 2024
AI Tools for Business and Startups
AI Tools for Scientists - Nakov (Oct 2023)
AI Tools for Entrepreneurs
Bulgarian Tech Industry - Nakov at Dev.BG All in One Conference 2023
AI Tools for Business and Personal Life
Дипломна работа: учебно съдържание по ООП - Светлин Наков
Дипломна работа: учебно съдържание по ООП
Свободно ИТ учебно съдържание за учители по програмиране и ИТ
AI and the Professions of the Future
Programming Languages Trends for 2023
IT Professions and How to Become a Developer
GitHub Actions (Nakov at RuseConf, Sept 2022)
Ad

High Quality Code Introduction

  • 1. Качествен програмен код Светлин Наков Национална академия по разработка на софтуер www.devbg.org
  • 2. Лекторът Светлин Наков Директор на Национална академия по разработка на софтуер (НАРС) Безплатни курсове за програмисти – Java и .NET Обучение по стипендия + осигурена работа Председател на Българска асоциация на разработчиците на софтуер (БАРС) Преподавател по съвременни софтуерни технологии в СУ &quot;Св. Климент Охридски&quot; Консултант по разработка на софтуер Носител на наградата &quot;Джон Атанасов&quot; на президента на България за 2004
  • 3. Съдържание Дефиниция за качествен код Софтуерен дизайн Висококачествени подпрограми Защитно програмиране Правилно използване на променливите Имената на променливите Преработка на съществуващ код Самодокументиращ се код
  • 4. Качествен програмен код Какво е качествен програмен код?
  • 5. Какво е качествен код? Качеството на софтуера има 2 аспекта: Външно качество – видимото за потребителя Коректност на софтуера Удобство и леснота за работа Производителност (скорост на работа) Вътрешно качество – вътрешната организация на архитектурата и програмния код Разбираемост Леснота за промяна и добавяне на функционалност (поддръжка) Простота на реализацията
  • 6. Какво е качествен код? Характеристики за качество на кода: Коректност Четимост и разбираемост Висока свързаност на отговорностите (strong cohesion) на всички нива (модули, класове, методи) Функционална независимост ( loose coupling) на всички нива (модули, класове, методи) Добро, консистентно форматиране Подходящо и консистентно именуване на класовете, методите, променливите и останалите елементи Добра документация, вградена в кода
  • 7. Качествен програмен код Какво е качествен софтуерен дизайн?
  • 8. Софтуерен дизайн Качеството на софтуера силно зависи от качеството на дизайна Дизайнът е трудно-дефинируем процес Итеративен, недетерминистичен Няма точна рецепта как да се прави Основна цел на дизайна: Да се управлява сложността на софтуера Основен похват при дизайна: Функционална декомпозиция на проблемите на всички нива
  • 9. Какво е софтуерен дизайн Софтуерният дизайн е: Принципна организация на софтуерната система Дизайнът се състои от: Архитектурен план Описва основните компоненти и подсистеми на системата и взаимодействието между тях Детайлен дизайн Описва вътрешната организация на отделните компоненти и подсистеми Описва класовете и методите в класовете
  • 10. Характеристики на дизайна Минимална сложност, леснота за разбиране Леснота за поддръжка (промяна и разширяване) Функционална независимост между подсистемите, компонентите и класовете ( loose coupling ) Преизползваемост ( reusability ) Висок a входна зависимост ( fan-in ) Някои utility класове се използват много често Ниска изходна зависимост ( fan-out ) Един клас да не ползва прекалено много други класове Минималност – да няма излишни части
  • 11. Процесът на дизайн Функционална декомпозиция: Разделяме системата на подсистеми Разделяме подсистемите на класове и ги подреждаме в пакети ( пространства от имена) Разделяме класовете в подпрограми (методи) Проектираме подпрограмите (чрез псевдокод) Не е необходимо да се прави паралелно във всички посоки Започва се от най-важната функционалност за клиента
  • 12. Фази на дизайна Разделяне на подсистемите на класове Идентификация на обектите и процесите от реалния свят Съпоставяне на класове за тези обекти Идентификация на връзките между обектите Проектиране на класова йерархия Използване на шаблони ( design patterns ) Скриване на възможно най-много имплементационни детайли
  • 13. Фази на дизайна Разделяне на класовете на методи Идентификация на действията, които всеки обект може да извършва (методи) Идентификация на съществените характеристики на обектите (атрибути) Скриване на възможно най-много имплементационни детайли (private методи) Максимална функционална независимост (loose coupling) Висока свързаност на отговорностите (strong cohesion)
  • 14. Фази на дизайна Проектиране на вътрешността на методите Най-често е отговорност на програмистите, а не на архитектите Подбор на подходящи алгоритми Описание на алгоритмите чрез псевдокод
  • 15. Силата на диаграмите Извикване на уеб услуга Изпълнение на SQL заявка Резултат от SQL заявка XML DataSet Променени данни във вид на XML DataSet Заявки за нанасяне на промените Клиент Web-услуга База данни
  • 16. Качествен програмен код Какво са качествените подпрограми (методи)?
  • 17. Защо да използваме методи? Намаляваме сложността Разбиваме сложните проблеми на по-прости Добавяме междинни нива на абстракция Скриваме детайли за имплементацията Намаляваме риска от неуспех Избягваме повторението на еднакъв код Скриваме сложни последователности от действия Скриваме работата с указатели Опростяваме сложни булеви проверки
  • 18. Свързаност на отговорностите Свързаност на отговорностите (strong cohesion) е ключово изискване за методите Генерален принцип: Един метод трябва да прави само едно нещо и да го прави добре Операциите в един метод трябва да са взаимосвързани – насочени към обща задача Идеалният случай: Функционална кохезия Методът извършва единична ясно дефинирана операция Пример: функция S qrt()
  • 19. Допустими видове кохезия Последователна кохезия Редица от стъпки за решаване на единна задача Пример: SendEmail() свързваме се към сървъра за поща изпращаме съобщението затваряме връзката Комуникационна кохезия Свързаност на действията по общи данни Пример: DisplayReport() извличаме данните форматираме ги отпечатваме ги
  • 20. Допустими видове кохезия Времева кохезия Действия, които се извършват по едно и също време Пример: LoadSettings() зареждаме настройките за шрифтовете зареждаме настройките за цветовете зареждаме настройките за принтера зареждаме настройките за базата данни зареждаме настройките за Интернет достъпа
  • 21. Недопустими видове кохезия Логическа кохезия Изпълнява се различно действие според някой входен параметър (код на операция) Лош пример: ReadAll(int op _code ) – прочита артикул, цена, адрес или ЕГН според подадения код Изключение: Обработчици на събития (event handlers) Случайна кохезия (липса на свързаност) Няколко несвързани едно с друго действия Изключително лоша практика!
  • 22. Имената на методите Името трябва да описва всичко, което методът извършва Ако няма подходящо име, имаме лоша кохезия ! Избягвайте безлични и общи думички Лош пример: HandleStuff() , ProcessData() Не използвайте цифри в името Лош пример: ReadProfile1() , ReadProfile2() Дължината на името трябва да е толкова дълга, колкото е необходимо (9-15 символа) Ако името е прекалено дълго, имаме лоша кохезия Използвайте английски език
  • 23. Имената на методите Имената на функциите трябва да описват връщаната стойност Пример: GetNumberOfProcessors() Имената на процедурите се съставят по схемата <глагол> + <обект> Пример: PrintReport() , LoadSettings() Използвайте консистентно противоположностите Пример: OpenFile() и CloseFile() Лош пример: OpenFile() и _descriptor_close() Използвайте конвенция за честите операции Пример: GetName() , GetAge() , SetName() , SetAge() Спазвайте конвенцията навсякъде
  • 24. Колко да са дълги методите? Предпочитайте кратки методи (до един екран) Методите трябва да имат силна кохезия Това е много по-важно от дължината им! Методите трябва да са дълги &quot;колкото трябва&quot; Не разделяйте на части даден метод само защото е много дълъг
  • 25. Параметрите на методите Подреждайте параметрите в последователност ( < входни > , <входно-изходни>, < изходни > ) Подреждайте консистентно параметрите при методи с подобни параметри Използвайте всички параметри Ако връщате статус или код за грешка, сложете този параметър последен Не използвайте параметрите като работни променливи (не модифицирайте параметрите) Документирайте неочевидните допускания Например мерната единица при подаване на числа
  • 26. Параметрите на методите Ограничете броя на параметрите до около 7 Човешкото съзнание не може да следи повече от 7 неща едновременно (знаехте ли това?) Разграничавайте входните от изходните параметри (ако езикът не го поддържа) Кога да подаваме обект и кога няколко негови полета? Съобразете се логически методът над какво работи – над обекти или над съвкупност от стойности Подавайте параметрите в коректния им ред Използвайте именувано извикване, ако се поддържа
  • 27. Функция или процедура Функция или процедура? Използвайте функция когато основната задача на метода е да изчисли и върне някаква стойност Уверете се, че всеки път на изпълнение връща стойност Не връщайте указател към локални данни Запазете в променлива стойността преди да я върнете: return days * hoursPerDay * ratePerHour; int salary = days * hoursPerDay * ratePerHour; return salary;
  • 28. Качествен програмен код Какво е защитно програмиране?
  • 29. Защитно програмиране Защитно програмиране ( defensive programming) Насочено към защита на кода от некоректни данни Пази кода от грешки, които никой не очаква Имплементира се чрез проверка на коректността на всички входни данни данните, идващи от външни източници входните параметри на методите Имплементира се чрез assertions, изключения и други средства за управление на грешки
  • 30. Проверки ( assertions) Проверките ( assertions) следят за различни очаквания за състоянието на програмата Улавят неочаквани входни параметри или вътрешни състояния Силно улесняват откриването на грешки в кода Представляват изрази от вида: assert( условие, съобщение _ за _ грешка) Ако условието е нарушено, програмата завършва аварийно и се отпечатва грешката При release компилация се премахват от кода Много са полезни при големи и сложни проекти
  • 31. Проверки ( assertions) Проверките на практика &quot;документират&quot; скритите допускания, които кодът очаква Някои езици поддържат assertions , в другите можем да си ги реализираме сами Типични грешки, улавяни с assertions: Стойност NULL на входен параметър Стойност извън допустимия диапазон за входен параметър Невалидно състояние на файл, поток или друг манипулатор на ресурс Излизане извън размера на масив или колекция
  • 32. Assertions – препоръки Използвайте изключения или друг механизъм за контрол на очакваните грешки Използвайте assertions само за грешки, които никога не трябва да се случват Не слагайте изпълним код в assertion Лош пример: assert(ConnectToDatabase(), &quot;Can not establish database connection!&quot;) Използвайте assertions за да документирате входни и изходни условия в методите Добавете код за управление на грешката след assertion (за по-голяма надеждност)
  • 33. Изключения ( exceptions ) Изключенията ( exceptions ) предоставят мощен механизъм за централизирано управление на грешки и непредвидени ситуации Позволяват проблемните ситуации да се обработват на много нива Улесняват писането и поддръжката на надежден програмен код Изключенията могат да бъдат класове – да се наследяват и да образуват йерархии Могат да се използват на мястото на assertions
  • 34. Изключения – препоръки Използвайте изключения, за да уведомите другите части на кода за проблеми, които не трябва да бъдат игнорирани Хвърляйте изключение само в ситуации, които наистина са изключителни и трябва да се обработят по някакъв начин Ако даден проблем може да се обработи локално, направете го и не хвърляйте изключение Хвърляйте изключенията на подходящо ниво на абстракция Пример: GetEmplyeeInfo() може да хвърля EmployeeException , но не и FileNotFoundException
  • 35. Изключения – препоръки Включвайте в съобщението на изключението пълно описание на причината за възникването му Всеки catch блок трябва да прихваща само изключенията, които очаква и знае как да обработва, а не всички Catch блоковете трябва да са подредени така, че да започват от изключенията най-ниско в йерархията и да продължават с по-общите Избягвайте празни catch блокове Не е правилно да прихващате всички изключения, без да ви интересува типа им
  • 36. Изключения – препоръки Очаквайте описаните в документацията изключения Документирайте изключенията, които вашият код може да предизвика Управлявайте всички необработени изключения централизирано Можете да покажете съобщение за проблем на потребителя и да запишете проблема в log файл Установете стандарти за изключенията в приложението и дефинирайте класова йерархия Хвърляйте само обекти от тип &quot;изключение&quot; или негови наследници, а не указатели и числа
  • 37. Колко защитно програмиране да оставим в Release версията Оставете кода, който проверява за важни грешки Премахнете кода, който проверява за маловажни грешки Премахнете кода, който предизвиква непосредствени сривове Заместете го с код, който прекратява програмата &quot;културно&quot;, без загуба на данни Непременно log- вайте грешките при клиента Ако показвате съобщения за проблеми на потребителя, съобразете се с неговите знания
  • 38. Качествен програмен код Как да използваме променливите?
  • 39. Принципи при инициализиране Проблемите: Неинициализирана променлива Пример: int value; Частично инициализирана променлива Пример: Student student = new Student(); Student.Name = &quot;Бай Мангал&quot;; // Student.Age – не е инициализирано
  • 40. Инициализирайте променливите в момента на деклариране Инициализирайте всяка променлива близо до мястото където се използва за пръв път Обръщайте специално внимание на променливите за броене и натрупване Инициализирайте член-променливите на един клас в конструктора Принципи при инициализиране
  • 41. Използвайте настройките на компилатора за автоматично инициализиране на променливите Включвайте предупредителните съобщения от компилатора Проверявайте входните параметри за валидност Проверявайте за невалидни указатели към паметта Инициализирайте работната памет в началото на програмата Принципи при инициализиране
  • 42. Обхват, живот, активност Обхват ( variable scope) – колко “известна” е една променлива Глобална (статична), член-променлива, локална Диапазон на активност (span) – среден брой линии между обръщенията към даден променлива Живот ( lifetime ) – обем на кода от първото до последното рефериране в даден метод Проследете къде се използва дадена променлива , нейният диапазон на активност и период на живот Направете обхвата, живота и активността на променливите колкото се може по-малки
  • 43. Работа с променливи Инициализирайте променливите извън тялото на цикъла Не инициализирайте променлива до момента, в който ще бъде използвана Групирайте сходните операции Започнете с най-малкия обхват и разширявайте, ако се наложи Използвайте всяка променлива точно и само за една цел Избягвайте променливи със скрито значение Използвайте всички декларирани променливи
  • 45. Качествен програмен код Как да именуваме променливите?
  • 46. Именуване на променливи Избирайте добро име! Името трябва да описва точно и ясно обекта, който променливата представлява Добри имена: account , blockSize , customerDiscount Лоши имена: r18pq , __hip , rcfd , val1 , val2 Адресирайте проблема, който решава променливата – “какво” вместо “как” Добри имена: employeeSalary , employees Лоши имена: myArray , customerFile , customerHashTable
  • 47. Именуване на променливи Оптимална дължина на името – 10 до 16 символа Изборът на име зависи от обхвата Променливите с по-голям обхват и по-дълъг живот имат по-дълго и описателно име: protected Account[] mCustomerAccounts; Променливите с малък обхват и кратък живот могат да са по-кратки: for (int i=0; i<customers.Length; i++) { … } Използвайте пространства за избягване на повторения при декларирането: System.Windows.Forms.TextBox System.Web.UI. Web Controls.TextBox
  • 48. Именуване на специфични типове данни Именуване на броячи Пример: UsersCount , RolesCount , FilesCount Именуване на променливи за състояние Пример: ThreadState , TransactionState Именуване на временни променливи Пример: index , value , count Лош пример: a , aa , tmpvar1 , tmpvar2 При булеви променливи използвайте имена, които дават предпоставка за истина или лъжа Пример: canRead , available , isOpen , valid
  • 49. Именуване на специфични типове данни Булевите променливи трябва да носят &quot;истина&quot; в името си Пример: isReady , canRead , hasMoreData Лош пример: notReady , cannotRead , noMoreData Именуване на изброими типове Използвайте вградените изброени типове (когато езикът за програмиране ги поддържа): Color.Red , Color.Yellow , Color.Blue Или използвайте подходящи префикси: colorRed , colorBlue , colorYellow Именуване на константи – с главни букви Пример: MAX_FORM_WIDTH , BUFFER_SIZE
  • 50. Кога е необходима конвенция за именуване Когато екипът е по-голям Когато програмата ще се поддържа дълго време Когато програмата ще се проверява от други програмисти във Вашата организация Когато програмата е прекалено голяма и е невъзможно да се проследят всички модули наведнъж Когато програмата ще спре да се развива за известно време Когато във вашия проект имате много непозната терминология, обща за целия проект
  • 51. Стандартни префикси Унгарска конвенция – използва се все по-рядко Дефинирани типове от потребителя Например: typedef int Color; Семантични префикси (напр. btnSave ) Не изпускайте букви за да съкратите името Съкращавайте по един и същ начин из целия код Създавайте имена, които да можете да произнесете (не като btnDfltSvRzlts ) Избягвайте комбинации, които водят до друга дума или различно значение ( напр. preFixStore )
  • 52. Стандартни префикси Документирайте кратките имена в кода Помнете, че имената са предназначени за хората, които ще четат кода, а не за тези които го пишат Избягвайте заблуждаващи имена или съкращения Избягвайте променливи с подобни имена, но с различно предназначение Например: UserStatus и User Current Status Избягвайте имена, които звучат еднакво Избягвайте цифри в имената ( напр. pi314 ) Избягвайте грешно написани думи в имената
  • 53. Стандартни префикси Избягвайте думи, които често се грешат Избягвайте използването на повече от един народен език Избягвайте използването на стандартни типове и ключови думи в имената на променливите Не използвайте имена, които нямат нищо общо с това което променливите съдържат Избягвайте имена, които съдържат трудни за четене символи
  • 54. Качествен програмен код Какво е преработка на кода ( Refactoring )?
  • 55. Митове и реалност за процеса за разработка на софтуер Митът Когато един проект стриктно спазва правилата на процеса за разработка, единствената последвала промяна в кода е само в периода на поддръжка на софтуера ( software maintenance phase) Така генерирането на код е праволинейно без да се налага преработка
  • 56. Митове и реалност за процеса за разработка на софтуер Реалността Кодът постоянно се променя Причината: променя се разбирането за проблемната област в хода развитие на проекта Всяка промяна в изискванията налага промени и в съществуващия код Дори в най-добре управляваните проекти
  • 57. Преработка на кода ( Refactoring) Еволюцията на софтуера наподобява биологичната еволюция Някои промени са с благоприятен ефект, други не са Еволюцията на софтуера е неизбежна Еволюцията е възможност да се приближим към “съвършения” продукт
  • 58. Преработка на кода ( Refactoring) Основно правило на еволюцията на софтуера: Еволюцията трябва да подобрява начина на реализация на даден проект Основният начин за реализиране на това правило: Преработката на кода
  • 59. Кога даден код се нуждае от преработка ? Повторение на код При проблеми в дублициран код се налага да се правят модификации на няколко места Даден метод е прекалено обемист Даден цикъл е прекалено обемист или съдържа дълбоко ниво на влагане Даден клас изпълнява несвързани отговорности ( poor cohesion) Даден клас не предоставя добро ниво на абстракция
  • 60. Кога даден код се нуждае от преработка ? (продължение) Даден метод има дълъг списък с параметри Една промяна налага паралелна модификация на няколко класа Свързани една с друга данни се използват винаги заедно, но не са обединени в клас Даден метод използва повече функционалност от други класове отколкото от собствения си Даден клас е прекалено обвързан с друг Полета на даден клас са public
  • 61. Преработка на код на ниво данни Заместете “вълшебните” числа и низове с именувана константа (напр. 1024  BUF_SIZE ) Преименувайте дадена променлива с по-ясно и по-информативно име ( p  currentPos ) Преработете даден условен израз в метод Използвайте междинни променливи за резултата от сложни изрази Преобразувайте обикновени данни в нов клас Групирайте свързаните константи в изброими типове (enumerations)
  • 62. Преработка на кода на ниво метод Преметете фрагмент от кода на даден метод в нов метод (extract method) Премахнете даден метод, ако кодът, който съдържа, е прекалено прост и кратък Преработете дълъг и сложен метод в няколко по-малки или в изцяло нов клас Премахнете неизползваните параметри Ако има нужда от допълнителен параметър за даден метод, добавете го
  • 63. Преработка на кода на ниво клас Променете обекти, подавани по стойност, с обекти, подавани по указател (референция) Изнесете общата функционалност за набор от класове в отделен базов клас Преместете метод от един клас в друг, ако той логически принадлежи на последния Преобразувайте един клас в два или повече Премахнете даден клас, ако не се ползва
  • 64. Преработка на кода на ниво система Създайте абстракция на данните, върху които нямате контрол Дефинирайте клас, който ще енкапсулира тези данни и чиито обекти ще бъдат подавани на потребителите Ако не е наложително, премахвайте цикличните зависимости между класовете Ако не е нужна употребата на изключения, използвайте кодове за грешки Използвайте &quot; factory метод&quot; за създаване на инстанции на даден клас според даден параметър
  • 65. Качествен програмен код Какво е самодокументиращ се код и как се реализира?
  • 66. Стилът на програмиране и документацията Документация на високо ниво Архитектурен план на системата Документация на ниско ниво Разглежда особености в най-големи детайли, касаещи кода на програмата Коментарите в кода не са основният източник на документация Добрият стил на програмиране е най-добрата документация! Самодокументиращ се код Лесно се разбира основната му цел
  • 67. Характеристики на самодокументиращия се код Добра структура на програмата – подравняване, организация на кода Използване на ясни и лесни за разбиране конструкции Употреба на подходящи имена на променливи, методи и класове Употреба на именувани константи, вместо “магически” константи и стрингове Минимизация на сложността на реализацията
  • 68. Самодокументиращ се код – важни въпроси Дава ли интерфейсът на класа добра абстракция? Подходящо ли е името на класа и показва ли основната му цел? Става ли ясно от интерфейса как трябва да се използва класа? Показва ли името на метода основната му цел? Всеки метод реализира ли една добре определена задача? Имената на променливите съответстват ли на тяхната употреба?
  • 69. Самодокументиращ се код – важни въпроси (продължение) Групирани ли са свързаните един с друг оператори? Само една задача ли изпълняват конструкциите за итерация (циклите)? Има ли дълбоко влагане на условни клаузи? Показва ли организацията на кода неговата логическата структура? Дизайнът недвусмислен и ясен ли е? Скрити ли са детайлите на имплементацията възможно най-много?
  • 70. “ Ефективни” коментари Коментарите понякога могат да навредят повече отколкото да помогнат Добрите коментари не повтарят кода и не го обясняват – те изясняват неговата идея Коментарите трябва да обясняват на по-високо ниво какво се опитваме да постигнем Писането на коментари помага да осмислим по-добре това, което искаме да реализираме
  • 71. Правила на “ефективните” коментари Използвайте псевдокод, когато е възможно Пишете коментари когато създавате самия код, а не след това Продуктивността не е добра причина за да не пишете коментари Документирайте всичко, което не става ясно от вашия код Поставянето на много коментари е толкова вредно колкото и липсата на такива Не коментирайте трудно разбираем код – по добре го преработете
  • 72. Ресурси по темата Code Complete, 2 nd edition, Steve McConnell, Microsoft Press, 2004, ISBN 0735619670 , http://www.cc2e.com/ Курс по &quot;Качествен програмен код&quot; в СУ – http://www.devbg.org/codecourse/

Editor's Notes

  • #40: The variable has never been assigned a value. Its value is whatever bits happened to be in its area of memory when the program started. The value in the variable is outdated. The variable was assigned a value at some point, but the value is no longer valid. Part of the variable has been assigned a value and part has not.
  • #41: Here are guidelines for avoiding initialization problems.
  • #43: The code between references to a variable is a “window of vulnerability.” In the window, new code might be added, inadvertently altering the variable, or someone reading the code might forget the value the variable is supposed to contain. It’s always a good idea to localize references to variables by keeping them close together.
  • #44: Doing this improves the chance that when you modify the loop, you’ll remember to make corresponding modifications to the loop initialization. Later, when you modify the program and put another loop around the initial loop, the initialization will work on each pass through the new loop rather than on only the first pass. C++ Example of Good Variable Declarations and Initializations int receiptIndex = 0; float dailyReceipts = TodaysReceipts(); double totalReceipts = TotalReceipts( dailyReceipts ); C++ Example of Using Two Sets of Variables in a Confusing Way void SummarizeData (...) { ... GetOldData( oldData, &amp;numOldData ); GetNewData( newData, &amp;numNewData ); totalOldData = Sum( oldData, numOldData ); totalNewData = Sum( newData, numNewData ); PrintOldDataSummary( oldData, totalOldData, numOldData ); PrintNewDataSummary( newData, totalNewData, numNewData ); SaveOldDataSummary( totalOldData, numOldData ); SaveNewDataSummary( totalNewData, numNewData ); ... } C++ Example of Using Two Sets of Variables More Understandably void SummarizeDaily( ... ) { GetOldData( oldData, &amp;numOldData ); totalOldData = Sum( oldData, numOldData ); PrintOldDataSummary( oldData, totalOldData, numOldData ); SaveOldDataSummary( totalOldData, numOldData ); ... GetNewData( newData, &amp;numNewData ); totalNewData = Sum( newData, numNewData ); PrintNewDataSummary( newData, totalNewData, numNewData ); SaveNewDataSummary( totalNewData, numNewData ); ... } Part of minimizing the scope of a variable is keeping it as local as possible. It is much more difficult to reduce the scope of a variable that has had a large scope than to expand the scope of a variable that has had a small scope—in other words, it’s harder to turn a global variable into a class variable than it is to turn a class variable into a global variable. It’s harder to turn a protected data member into a private data member than vice versa. Use each variable for one purpose only It’s sometimes tempting to use one variable in two different places for two different activities. Usually, the variable is named inappropriately for one of its uses, or a “temporary” variable is used in both cases (with the usual unhelpful name x or temp ). Avoid variables with hidden meanings Another way in which a variable can be used for more than one purpose is to have different values for the variable mean different things. Make sure that all declared variables are used The opposite of using a variable for more than one purpose is not using it at all. A study by Card, Church, and Agresti found that unreferenced variables were correlated with higher fault rates (1986).
  • #47: Considerations in Choosing Good Names 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. Unlike the dog and its name, which are different entities, a variable and a variable’s name are essentially the same thing. The Most Important Naming Consideration The most important consideration in naming a variable is that the name fully and accurately describe the entity the variable represents. An effective technique for coming up with a good name is to state in words what the variable represents. Problem-Orientation A good mnemonic name generally speaks to the problem rather than the solution. A good name tends to express the what more than the how . In general, if a name refers to some aspect of computing rather than to the problem, it’s a how rather than a what . Avoid such a name in favor of a name that refers to the problem itself. Optimum Name Length Gorla, Benander, and Benander found that the effort required to debug a program was minimized when variables had names that averaged 10 to 16 characters (1990). Programs with names averaging 8 to 20 characters were almost as easy to debug. The guideline doesn’t mean that you should try to make all of your variable names 9 to 15 or 10 to 16 characters long. It does mean that if you look over your code and see many names that are shorter, you should check to be sure that the names are as clear as they need to be. The Effect of Scope on Variable Names Are short variable names always bad? No, not always. When you give a variable a short name like i , the length itself says something about the variable—namely, that the variable is a scratch value with a limited scope of operation. Use qualifiers on names that are in the global namespace Common Opposites in Variable Names
  • #48: Considerations in Choosing Good Names 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. Unlike the dog and its name, which are different entities, a variable and a variable’s name are essentially the same thing. The Most Important Naming Consideration The most important consideration in naming a variable is that the name fully and accurately describe the entity the variable represents. An effective technique for coming up with a good name is to state in words what the variable represents. Problem-Orientation A good mnemonic name generally speaks to the problem rather than the solution. A good name tends to express the what more than the how . In general, if a name refers to some aspect of computing rather than to the problem, it’s a how rather than a what . Avoid such a name in favor of a name that refers to the problem itself. Optimum Name Length Gorla, Benander, and Benander found that the effort required to debug a program was minimized when variables had names that averaged 10 to 16 characters (1990). Programs with names averaging 8 to 20 characters were almost as easy to debug. The guideline doesn’t mean that you should try to make all of your variable names 9 to 15 or 10 to 16 characters long. It does mean that if you look over your code and see many names that are shorter, you should check to be sure that the names are as clear as they need to be. The Effect of Scope on Variable Names Are short variable names always bad? No, not always. When you give a variable a short name like i , the length itself says something about the variable—namely, that the variable is a scratch value with a limited scope of operation. Use qualifiers on names that are in the global namespace Common Opposites in Variable Names
  • #49: If the loop is longer than a few lines, it’s easy to forget what i is supposed to stand for, and you’re better off giving the loop index a more meaningful name. Because code is so often changed, expanded, and copied into other programs, many experienced programmers avoid names like i altogether. Java Example of Good Loop Names in a Nested Loop for ( teamIndex = 0; teamIndex &lt; teamCount; teamIndex++ ) { for ( eventIndex = 0; eventIndex &lt; eventCount[ teamIndex ]; eventIndex++ ) { score[ teamIndex ][ eventIndex ] = 0; } } C++ Examples of Cryptic Flags if ( flag ) ... if ( statusFlag &amp; 0x0F ) ... if ( printFlag == 16 ) ... if ( computeFlag == 0 ) ... flag = 0x1; statusFlag = 0x80; printFlag = 16; computeFlag = 0; C++ Examples of Better Use of Status Variables if ( dataReady ) ... if ( characterType &amp; PRINTABLE_CHAR ) ... if ( reportType == ReportType_Annual ) ... if ( recalcNeeded == True ) ... dataReady = True; characterType = CONTROL_CHARACTER; reportType = ReportType_Annual; recalcNeeded = False; Moreover, because the variables are officially given a “temporary” status, programmers tend to treat them more casually than other variables, increasing the chance of errors. Names like done and success are good boolean names because the state is either True or False ; something is done or it isn’t; it’s a success or it isn’t. Names like status and sourceFile , on the other hand, are poor boolean names because they’re not obviously True or False . Negative names like notFound , notdone , and notSuccessful are difficult to read when they are negated—for example, if not notFound When you use an enumerated type, you can ensure that it’s clear that members of the type all belong to the same group by using a group prefix, such as Color_ , Planet_ , or Month_ . When naming constants, name the abstract entity the constant represents rather than the number the constant refers to. FIVE is a bad name for a constant (regardless of whether the value it represents is 5.0 ). CYCLES_NEEDED is a good name.
  • #50: If the loop is longer than a few lines, it’s easy to forget what i is supposed to stand for, and you’re better off giving the loop index a more meaningful name. Because code is so often changed, expanded, and copied into other programs, many experienced programmers avoid names like i altogether. Java Example of Good Loop Names in a Nested Loop for ( teamIndex = 0; teamIndex &lt; teamCount; teamIndex++ ) { for ( eventIndex = 0; eventIndex &lt; eventCount[ teamIndex ]; eventIndex++ ) { score[ teamIndex ][ eventIndex ] = 0; } } C++ Examples of Cryptic Flags if ( flag ) ... if ( statusFlag &amp; 0x0F ) ... if ( printFlag == 16 ) ... if ( computeFlag == 0 ) ... flag = 0x1; statusFlag = 0x80; printFlag = 16; computeFlag = 0; C++ Examples of Better Use of Status Variables if ( dataReady ) ... if ( characterType &amp; PRINTABLE_CHAR ) ... if ( reportType == ReportType_Annual ) ... if ( recalcNeeded == True ) ... dataReady = True; characterType = CONTROL_CHARACTER; reportType = ReportType_Annual; recalcNeeded = False; Moreover, because the variables are officially given a “temporary” status, programmers tend to treat them more casually than other variables, increasing the chance of errors. Names like done and success are good boolean names because the state is either True or False ; something is done or it isn’t; it’s a success or it isn’t. Names like status and sourceFile , on the other hand, are poor boolean names because they’re not obviously True or False . Negative names like notFound , notdone , and notSuccessful are difficult to read when they are negated—for example, if not notFound When you use an enumerated type, you can ensure that it’s clear that members of the type all belong to the same group by using a group prefix, such as Color_ , Planet_ , or Month_ . When naming constants, name the abstract entity the constant represents rather than the number the constant refers to. FIVE is a bad name for a constant (regardless of whether the value it represents is 5.0 ). CYCLES_NEEDED is a good name.
  • #51: There are no hard-and-fast rules for when you should establish a naming convention, but here are a few cases in which conventions are worthwhile:
  • #52: Standardizing prefixes for common meanings provides a terse but consistent and readable approach to naming data. The best known scheme for standardizing prefixes is the Hungarian naming convention, which is a set of detailed guidelines for naming variables and routines (not Hungarians!) that was widely used at one time in Microsoft Windows programming. Although the Hungarian naming convention is no longer in widespread use, the basic idea of standardizing on terse, precise abbreviations continues to have value. Standardized Prefixes are composed of two parts: the user-defined–data type (UDT) abbreviation and the semantic prefix. User-Defined–Type (UDT) Abbreviation The UDT abbreviation identifies the data type of the object or variable being named. UDT abbreviations might refer to entities such as windows, screen regions, and fonts. A UDT abbreviation generally doesn’t refer to any of the predefined data types offered by the programming language. UDT Abbreviation Meaning ch Character (a character not in the C++ sense, but in the sense of the data type a word-processing program would use to represent a character in a document) doc Document pa Paragraph scr Screen region sel Selection wn Window Semantic Prefix Semantic prefixes go a step beyond the UDT and describe how the variable or object is used. Unlike UDTs, which vary project to project, semantic prefixes are somewhat standard across projects. Table 11-7 shows a list of standard semantic prefixes. Semantic Meaning Prefix c Count (as in the number of records, characters, and so on) first The first element that needs to be dealt with in an array. first is similar to min but relative to the current operation rather than to the array itself. g Global variable i Index into an array last The last element that needs to be dealt with in an array. last is the counterpart of first . lim The upper limit of elements that need to be dealt with in an array. lim is not a valid index. Like last , lim is used as a counterpart of first . Unlike last , lim represents a noninclusive upper bound on the array; last represents a final, legal element. Generally, lim equals last + 1 . m Class-level variable max The absolute last element in an array or other kind of list. max refers to the array itself rather than to operations on the array. min The absolute first element in an array or other kind of list. p Pointer
  • #54: Avoid misleading names or abbreviations Be sure that a name is unambiguous. For example, FALSE is usually the opposite of TRUE and would be a bad abbreviation for “Fig and Almond Season.” Avoid names with similar meanings If you can switch the names of two variables without hurting the program, you need to rename both variables. Avoid variables with different meanings but similar names If you have two variables with similar names and different meanings, try to rename one of them or change your abbreviations. Avoid names like clientRecs and clientReps . Avoid names that sound similar, such as wrap and rap Homonyms get in the way when you try to discuss your code with others. One of my pet peeves about Extreme Programming (Beck 2000) is its overly clever use of the terms Goal Donor and Gold Owner, Avoid numerals in names If the numerals in a name are really significant, use an array instead of separate variables. If an array is inappropriate, numerals are even more inappropriate. Avoid misspelled words in names It’s hard enough to remember how words are supposed to be spelled. To require people to remember “correct” misspellings is simply too much to ask. For example, misspelling highlight as hilite to save three characters makes it devilishly difficult for a reader to remember how highlight was misspelled. Avoid words that are commonly misspelled in English Absense, acummulate, acsend, calender, concieve, defferred, definate, independance, occassionally, prefered, reciept, superseed, and many others are common misspellings in English. Most English handbooks contain a list of commonly misspelled words. Avoid using such words in your variable names. Avoid multiple natural languages In multi-national projects, enforce use of a single natural language for all code including class names, variable names, and so on. Reading another programmer’s code can be a challenge; reading another programmer’s code in Southeast Martian is impossible. Avoid the names of standard types, variables, and routines For example, the following code fragment is legal in PL/I, but you would be a certifiable idiot to use it: if if = then then then = else; else else = if;
  • #56: Myth : a well mange software project conducts methological requirements development and defines a stable list of the program’s responsibilities. Design follows requiremnets, and it is done carefully so that coding can proceed linearly , from start to finish, implying that most of the code can be writeen once, tested, and forgotten. Accoring to the myth, the only time that the code is significantly modified is during the software – maintenance phase, something that happens only after the initial version of a system has been delivered Reality : code eevolves substantially during its initial development. Many of the changes seen during initial coding are at least as dramatic as changes seen during maintenance. Coding, debugging and unit testing consume between 30 to 65 percent of the effort on a typycal project, depending on the project’s size. If coding and unit testing were straight-forward processes, they would consume no more than 20 – 30 percent of the total effort on a project. Even on well-managed project, however, requirements change by about one to four percent per month. Requirements changes invariably cause corresponding code changes – sometimes substantial code changes.
  • #57: Myth : a well mange software project conducts methological requirements development and defines a stable list of the program’s responsibilities. Design follows requiremnets, and it is done carefully so that coding can proceed linearly , from start to finish, implying that most of the code can be writeen once, tested, and forgotten. Accoring to the myth, the only time that the code is significantly modified is during the software – maintenance phase, something that happens only after the initial version of a system has been delivered Reality : code eevolves substantially during its initial development. Many of the changes seen during initial coding are at least as dramatic as changes seen during maintenance. Coding, debugging and unit testing consume between 30 to 65 percent of the effort on a typycal project, depending on the project’s size. If coding and unit testing were straight-forward processes, they would consume no more than 20 – 30 percent of the total effort on a project. Even on well-managed project, however, requirements change by about one to four percent per month. Requirements changes invariably cause corresponding code changes – sometimes substantial code changes.
  • #60: Повторение на код – при наличие на проблеми в такъв код се налага да се правят модификации на много места Даден метод е прекалено обемист – един метод не трябва да е по - дълъг от един екран; един начин да подобрим системата е да повишиме нейната модуларност чрез добре дефинирани методи, изпълняващи ясни и точни задачи. Даден цикъл е прекалено обемист – тялото на даден цикъл е добър кандидат за декларирането на отделен метод, като по този начин се намаля сложността на даден цикъл Даден клас има несвързани отговорности – ако намерите клас който реализира несвързани едни с други отговорности, вероятно този клас е по добре да се раздели на няколко различни класове, всеки от които има ясна група от взаимосвързани отговорности Даден клас не предоставя добро ниво на абстракция – интерфейсите на класовете имат склонност да еволюират в течение на времето (във време на бързи промени, които целят предимно бързото реализиране). По този начин интерфейсът на класа в даден момент става като чудовището Франкенщайн – грозен и труден за интелектуална поддръжка
  • #61: Даден метод има дълъг списък с променливи – обикновено се счита че списъкът с параметри не бива да надвишава 7. Една промяна налага паралелна модификация на няколко класове – например ако имплементирането на един специален формат налага модификацията на 15 класове които ще го използват, това е добър момент в който ние можем да се замислим дали не можем да създадем нов клас който енкапсулира този нов формат Свързани една с друга данни се използват заедно, но не са обединени в клас – ако подаднете в ситуация в която променяте една обща група от променливи, вероятно е добре да помислите да обедините тези данни в отделен клас Даден метод използва повече функционалност от други класове отколкото от собствения си – това предполага че метода трябва да бъде преместен в друг клас Даден клас е прекалено обвързан с друг – encapsulation/ енкапсулацията ( information hiding ) е вероятно най-силното средство с което можете да направите вашите програми лесно поддържани и да намалите вероятността от каскадни промени в кода. Всеки път когато видите че даден клас знае прекалено много за даден друг клас, по добре се насочете към по силна енкапсулация отколкото към по – слаба Полета на даден клас са public – public data members винаги заличават разликата м/у интерфейс и имплементация и най-вече нарушават правилата за енкапсулация и ограничават гъвкавостта на кода.
  • #62: Преработете даден условен израз в метод – обикновено това се прави в случаи в които този израз се среща на много други места; друга употреба е когато условния израз е много сложен и включва в себе си други изрази Използвайте междинни променливи – присвоете на променлива стойността на даден израз и и дайте подходящо име така че то да обяснява целта на този израз Преобразувайте обикновени данни ( data primitive ) в нов клас – ако обикновенните данни се нуждаят от обслужваща функционалност, преобразувайте данните в обект и добавете функционалността от която се нуждае; по този начин се постига и по добра типова сигурност и валидност ( stricter type checking ) Групирайте свързаните константи в изброими типове ( enumerations ) – спомага за по стриктна провека на типа, след като е обособен като клас, или ако е във вид на изброим тип – тогава кода е по-изразен
  • #63: Извадете определен код от даден метод и го превърнете в нов метод – в случай че използвате даден кодов блок многократно, по добре създайте нов метод който съдържа тази функционалност и извиквайте директно новия метод Премахнете даден метод, ако кода който съдържа е прекалено елементарен и кратък – вземете кода от тялото на даден метод ако той е прост и самоописващ се Преработете дълъг метод в няколко по малки или в изцяло нов клас – ако един метод има прекалено голяма имплементация, помислете дали този метод не може да се раздели на отделни самостоятелни подчасти, който да бъдат по-нататък реализирани в нови методи или в отделен клас Ако има нужда от допълнителен параметър за даден метод добавете го към списъка с параметри – вместо да се използват глобални променливи, предпочита се добавянето на нов параметър към списъка с параметри на даден метод
  • #64: Променете обекти подавани по стойност с такива подавани по указател – поддръжката на копия на един клас е трудно и непосилно; това оказва и влияние върху производителността на системата като цяло (особено в C++ ) Преобразувайте един клас в два – ако даден клас има 2 различни по типа си отгороности, по добре е да разделите класа на 2 отделни такива че всеки да има точно определени и взаимосвързани отговорности Премахнете даден клас – ако даден клас не върши много работа, просто го премахнете
  • #65: Създайте абстракция на данните върху които нямате контрол – пример е данните които са въведени през GUI-to и които се подават по късно на дадени контроли – в такъв момент можем да създадем отделен клас CControlData който да представлява данните който искаме да получим ( CControlData ще предоставя интерфейс за заявка и достъп до данните, като евентуално ще бъде възможно не винаги да се връщат валидни данни) Използвайте factory метод – кагато е необходимо да създадем определена инстанция на клас, според дадена константа или флаг, тогава е по добре да създадем статичен factory метод който ще връща правилните инстанции според подадения параметър
  • #67: Характеристики на добрия стил на програмиране: добра структура на програмата използване на ясни и лесни за разбиране похвати добри имена на променливи, методи и класове използване на именувани константи, вместо “магически” числа или стрингове добро структуриране на кода минимизация на сложността на реализация
  • #71: Писането на коментари ви помага да осмислите по-добре това което искате да реализирате – ако не можете да напишете лесно коментари, това е ясен знак че вие не разбирате добре това което трябва да реализирате