Шість рад з написання більш зрозумілого програмного коду - програмні продукти

Джефф Вогел, Президент, Spiderweb Software

Я пройшов важкий шлях, перш ніж навчився писати ясний програмний код, що допускає подальше обслуговування. Протягом останніх дванадцяти років я заробляв на життя, створюючи комп'ютерні ігри і продаючи їх через Інтернет відповідно до маркетингової моделлю під назвою shareware. Це означає, що я сідаю перед порожнім екраном і починаю малювати програмного коду - і тільки написавши кілька десятків тисяч рядків коду, я отримую щось, що можна продати.

Іншими словами, якщо я створюю безлад, то роблю це виключно в своєму власному гнізді. Коли о третій годині ночі я шукаю помилку в заплутаній як страшний сон "макаронної" програмою і кажу собі: «О господи, яке дебильное породження близькоспорідненого схрещування написало цей кошмар?», Відповідь на це питання може бути тільки один: «Це я сам» .

Освоєння хороших, упорядкованих методик програмування стало для мене справжнім благословенням. Деякі з таких методик описуються в даній статті. Багато вмілі, досвідчені і внутрішньо дисципліновані програмісти знають багато з подібних методик назубок. Все, що такі люди зможуть отримати з даної статті - це можливість насолодитися моїм чудовим літературним стилем та згадати, наскільки жахлива була їхнє життя до того, як вони долучилися до концепції «чистого коду».

В якості ілюстрації в цій статті я буду розглядати програму, що представляє собою гіпотетичну комп'ютерну гру під назвою Kill Bad Aliens (Убий поганого інопланетянина). Учасник цієї гри повинен керувати космічним кораблем. Цей корабель переміщається по горизонталі в нижній частині екрана і стріляє снарядами по вертикалі. Управління цим космічним кораблем буде здійснюватися, скажімо, за допомогою клавіатури.

Мал. 1. Наша гіпотетична гра

Шість рад з написання більш зрозумілого програмного коду - програмні продукти

Гра розбита на тимчасові інтервали - так звані «хвилі». Протягом кожної хвилі у верхній частині екрану один за одним з'являються інопланетяни. Вони літають по екрану і кидають бомби. Інопланетяни з'являються на екрані протягом фіксованого проміжку часу. Хвиля закінчується після того, як гравець знищив певну кількість інопланетян.

За кожного вбитого інопланетянина гравцеві нараховується певна кількість балів. Після закінчення кожної хвилі гравець отримує певну кількість бонусних очок - в залежності від того, наскільки швидко він зміг завершити дану хвилю.

При ураженні космічного корабля бомбою він вибухає, після чого на екрані з'являється інший корабель. Якщо космічний корабель вибухає три рази, гра завершується. Гравець, який набрав найбільшу кількість балів, вноситься в підсумковий рейтинг. При малій кількості набраних балів цього не відбувається.

Отже, ви сідаєте за робочий стіл і починаєте писати програмний код гри Kill Bad Aliens на мові C ++. Спочатку ви визначаєте об'єкти, які будуть представляти космічний корабель, снаряди гравця, інопланетян і їх бомби. Потім ви пишете код для графічного представлення на екрані всіх зазначених об'єктів. Після цього ви пишете код для переміщення об'єктів по екрану в залежності від часу. І, нарешті, ви пишете ігрову логіку, штучний інтелект інопланетян, програмний код для введення з клавіатури команд гравця і т.д.

Виникає неминуче питання - як зробити все перераховане таким чином, щоб програмний код готової гри був зрозумілим, не створював складнощів при подальшому супроводі і взагалі не був нерозбірливу плутанину?

І вже якщо ми підійшли до цього, ще одна рекомендація - ніколи не робіть такого:

Отже, все, що нам необхідно - це опис на початку і кілька «покажчиків» всередині, що пояснюють обраний шлях. Все це робиться дуже швидко і в тривалій перспективі економить масу часу.

Нижче наведено приклад з нашої гіпотетичної гри Kill Bad Aliens. Розглянемо об'єкт, який представляє снаряди, якими стріляє гравець. Вам доведеться часто викликати функцію, яка буде переміщати цей об'єкт вгору і визначати, куди він потрапив. Я б написав цю процедуру приблизно так:

Шість рад з написання більш зрозумілого програмного коду - програмні продукти

Припустимо, в нашій гіпотетичній грі ми хочемо, щоб при кожному попаданні в інопланетянина гравцеві нараховувалося десять балів. Це завдання можна вирішити двома способами. Поганий спосіб:

Хороший спосіб: У деякому глобальному файлі напишіть наступний рядок:

Тепер, якщо захочемо привласнити гравцеві кілька призових балів, досить написати наступне:

Більшість програмістів так чи інакше знають, що треба робити саме так. Однак для послідовної реалізації цієї концепції необхідна внутрішня дисципліна. Майже кожен раз, коли ви вводите числову константу, треба ретельно обміркувати - не поставити її в деякому «центральному пункті». Припустимо, наприклад, що ви хочете мати ігрову область з розмірами 800 х 600 пікселів. Настійно рекомендую ставити розміри цієї області наступним чином:

При роботі над грою Kill Bad Aliens мені потрібно вирішити, скільки інопланетян необхідно вбити для завершення хвилі, скільки інопланетян може перебувати на екрані одночасно і як швидко інопланетяни з'являються на екрані. Наприклад, якщо я захочу, щоб кожна хвиля мала однакове число інопланетян, що з'являються з однаковою частотою, я, швидше за все, напишу що-небудь подібне:

Цілком зрозумілий код. Потім, якщо я прийду до висновку про те, що хвилі занадто короткі, або що проміжки між появами інопланетян занадто малі, я зможу змінити ці значення і миттєво перенастроювати всю гру.

Одне з істотних переваг подібного механізму для завдання параметрів гри - така можливість швидкого внесення змін доставляє щире задоволення і дозволяє відчувати себе всемогутнім. Наприклад, ви змінили вищенаведений текст наступним чином:

Тепер достатньо однієї перекомпіляції - і ваша гра стане значно веселіше і навіть безумніше.

Мал. 2. Гра Kill Bad Aliens до зміни констант

Шість рад з написання більш зрозумілого програмного коду - програмні продукти

Мал. 3. Гра Kill Bad Aliens після збільшення всіх констант (грати, може бути, важкувато, але подивитися цікаво)

Шість рад з написання більш зрозумілого програмного коду - програмні продукти

Кінцева мета проста: написати програмний код таким чином, щоб стороння людина, що не має уявлення про те, що цей код робить, міг зрозуміти його якомога швидше.

При цьому необхідно дотримуватися золотої середини. Давайте своїм об'єктам імена, досить довгі і наочні для розуміння їхнього змісту, проте не настільки довгі і громіздкі, щоб це ускладнювало читання програмного коду.

Наприклад, в реальних умовах я, швидше за все не давав би констант такі довгі, які я застосував в попередньому розділі. Я зробив це тільки для того, щоб читач зміг повністю усвідомити їх зміст без будь-якого контексту. В контексті самої програми замість тексту:

я, швидше за все, написав би наступне:

Будь-яке непорозуміння, обумовлене більш коротким ім'ям, буде усунуто дуже швидко, а «читаність» коду покращиться.

Тепер розглянемо фрагмент коду, який буде використовуватися дуже часто для переміщення всіх інопланетян по екрану. Я майже напевно написав би це так:

Зверніть увагу, що масив для всіх інопланетян так і називається - aliens. І це дуже добре. Дане ім'я передає саме те, що я хотів сказати, однак при цьому воно досить короткий, щоб я міг ввести його з клавіатури тисячі разів і не зійти при цьому з розуму. Цілком ймовірно, ви будете використовувати цей масив ДУЖЕ ЧАСТО. Якщо ви назвете цей масив all_aliens_currently_on_screen. ваш програмний код стане на десять миль довше і настільки ж незрозумілішим.

Безсумнівно, до іменування змінних можна ставитися набагато серйозніше. Наприклад, існує т.зв. угорська нотація. Ця концепція має безліч варіантів, однак її основна ідея полягає в тому, що кожне ім'я змінної повинно починатися з префікса, що вказує на тип цієї змінної. (Наприклад, всі змінні типу unsigned long variable повинні починатися з префікса ul і т.д.). На мій погляд, це вже перебір, однак вам треба знати про існування і такого варіанту. Можна витратити досить багато часу на те, щоб зробити речі більш зрозумілою, проте рішення цього завдання також вимагає певних зусиль.

Якщо ваша програма досить велика, в ній напевно буде безліч функцій і процедур. Як би це не здавалося клопітно, кожну функцію / процедуру необхідно перевіряти на наявність помилок.

При створенні будь-якої процедури / функції завжди потрібно шукати відповіді на запитання: «Припустимо, якийсь несамовитий лиходій передасть в неї самі невідповідні значення. Як цей м'який і пухнастий шматочок коду зможе захистити себе і зберегти всю програму від пошкодження? »Потім напишіть свій код так, щоб він перевіряв цю процедуру / функцію на наявність таких ворожих даних і захищався від них.

Розглянемо наступний приклад. Основне завдання нашої чудової космічної гри полягає в тому, щоб вбивати інопланетян і набирати бали, тому нам необхідна процедура для зміни набраних гравцем балів. Більш того, при додаванні гравцеві балів ми хотіли б викликати процедуру, яка розцвічує б підсумковий рахунок красивими іскорками. Перший варіант виглядає наступним чином:

Поки все йде нормально. Тепер задайте собі питання: «Що в цьому коді може бути не так?»

По-перше, один очевидний момент. Що станеться, якщо змінна num_points матиме від'ємне значення? Чи можемо ми допустити, щоб рахунок гравця знижувався? Можливо. Однак в описі гри я до цього ніде не згадував про можливість втрати гравцем балів. Крім того, ігри повинні приносити задоволення, а втрата балів цьому суперечить. Таким чином, ми приходимо до висновку, що негативне число очок - це помилка, яку необхідно зловити.

Цей приклад був досить простим. Існує і менш очевидна проблема (з якої я постійно стикаюся в своїх іграх). Що станеться, якщо змінна num_points буде дорівнює нулю?

Це вельми правдоподібна ситуація. Не забудьте, що після закінчення кожної хвилі ми даємо гравцеві бонусні бали в залежності від швидкості її проходження. Що станеться, якщо гравець буде діяти занадто повільно і ми вирішимо дати йому 0 балів? Цілком ймовірно, що, працюючи над своїм кодом о 3 годині ночі, ви вирішите викликати процедуру change_score і передати їй значення 0.

В даному випадку проблема полягає в тому, що ми, швидше за все, не захочемо розцвічувати підсумковий рахунок святковими квітами, якщо кількість балів не змінилося. Таким чином, цю ситуацію також необхідно виявляти. Давайте спробуємо такий код:

Ну ось. Так набагато краще.

Зверніть увагу, що це була дуже проста функція. У ній зовсім відсутні всякі новомодні покажчики, які так люблять використовувати молоді лихі програмісти. Якщо ви передаєте масиви або покажчики, вам ОБОВ'ЯЗКОВО потрібно передбачити виявлення помилок або поганих даних.

Переваги такого підходу не вичерпуються захистом вашої програми від збоїв. Хороші механізми перевірки на наявність помилок також прискорюють налагодження. Припустимо, ви дізналися, що десь відбувається запис даних за межами деякого масиву, і переглядаєте свій код в пошуках місця, де це може відбуватися. Якщо в будь-якої процедури всі механізми перевірки є в наявності, вам не доведеться проходити її крок за кроком в пошуках помилки.

Цей підхід економить масу часу і заслуговує регулярного застосування. Час - наш найцінніший ресурс.

Цю фразу придумав не я. Однак вона є у Вікіпедії, і тому, по всій видимості, не позбавлена ​​сенсу.

Якщо ваша кінцева мета не полягає в тому, щоб мучити людей, то при написанні програмного коду слід прагнути до максимальної зрозумілості. Простий код вимагає менше часу на написання, на розуміння при наступному зверненні до нього і на налагодження.

Оптимізація - ворог ясності. Слід, однак, відзначити, що в деяких випадках оптимізація необхідна. Це особливо справедливо для ігор. Однак - і це дуже важливо - майже ніколи не відомо заздалегідь, що саме необхідно оптимізувати, до тих пір, поки ви не протестуєте реально функціонуючий код за допомогою інструменту під назвою профайлер. (Профайлер - це програма, яка спостерігає за вашою програмою і оцінює час, що витрачається на виконання окремих викликів. Існують чудові програми цього типу. Знайдіть і користуйтеся.)

Кожен раз, коли я беруся за оптимізацію будь-якої своєї гри, я незмінно приходжу в здивування. Код, про який я турбувався найбільше, завжди відмінно працює - а гальмує код, про який я ніколи не замислювався. Оскільки я не мав уявлення про те, який код виявиться швидким, а який - повільним, час, витрачений на оптимізацію до отримання реальних даних, виявляється витрачено даремно. Насправді справа йде ще гірше - крім марно втраченого часу така оптимізація призводить до заплутування програмного коду.

Дотримуватись цього правила непросто. З іншого боку, інакше воно не було б правилом. «Хороших» програмістів незграбний код дратує, навіть якщо він працює швидше.

Однак зрадійте! Після моїх довгих проповідей про те, що ви повинні витрачати більше часу на те і більше часу на це, настав рідкісний, воістину безцінний момент, коли я говорю вам, що невелика лінь цілком допустима!

Напишіть ясну і працюючу програму. Згодом у вас буде достатньо часу, щоб спаплюжити її за допомогою оптимізації. Однак не робіть цього до тих пір, поки не будете абсолютно впевнені, що робите правильно.

І, нарешті, якщо вже ми заговорили про хворобливому, ось мій завершальний рада:

Можливо, ви чули про існування заходи під назвою International Obfuscated C Code Contest - міжнародного конкурсу з самого заплутаної програмного коду на мові C. Вся справа в тому, що мови C і C ++, при всіх своїх перевагах, дозволяють створювати жахливо заплутаний код. Цей конкурс демонструє переваги зрозумілого коду «від противного» - за допомогою нагородження самих божевільних програмістів. Чудова ідея.

З результатами цього конкурсу варто ознайомитися хоча б для того, щоб зрозуміти, якої величезної шкоди можна завдати за допомогою енциклопедичних знань мови програмування, доповнених відсутністю елементарного сорому. При наявності достатніх знань ви зможете втиснути десять рядків нормального коду в один рядок. Ціна за це буде невисока - всього лише повна неможливість швидко виправити помилку в цьому коді.

Основний урок полягає в наступному. Якщо створюваний код вимагає від вас детального знання хитромудрих правил пріоритету або змушує вас заглядати в останні глави будь-якої книги, щоб зрозуміти, що саме ви робите - це означає, що ви почали мудрувати.

Кожен програміст має свій допустимий рівень складності створюваного коду. Особисто я пишу свої програми так, як водить машину типова бабуся. На мій погляд, якщо ваш код на C вимагає розуміння тонких відмінностей між виразами i ++ і ++ i, то він занадто складний.

Якщо хочете, можете вважати мене занудою. Ви маєте рацію. Однак я витрачаю на розуміння свого коду набагато менше часу, ніж міг би.

Однак не треба думати, що все викладене очевидно кожному. Насправді це не так. Погано написаний програмний код з'являється знову і знову - хоча без цього можна було б обійтися.

Якщо ви маєте справу з величезними обсягами програмного коду і не хочете загинути під його вантажем, я щиро сподіваюся, що мої поради вам допоможуть. Прагніть до простоти і ясності - це збереже вам масу часу і позбавить від непотрібних страждань.