- •Конструювання програмного забезпечення.
- •До вивчення дисципліни
- •1. Основи конструювання програмного забезпечення.
- •1.1. Місце кпз в життєвому циклі програмної системи.
- •1.2. Фундаментальні складові конструювання програмного забезпечення.
- •1.3. Мінімізація складності (Minimizing Complexity)
- •1.4. Очікування змін (Anticipating Changes)
- •1.5. Конструювання з можливістю перевірки (Constructing for Verification)
- •2. Стандарти у конструюванні
- •3. Високоякісне кодування.
- •3.1. Правила написання якісного коду. Рівень класів.
- •3.2. Принципи використання змінних.
- •3.3. Структурне програмування
- •4. Удосконалення програмного забезпечення.
- •4.1. Рефакторинг
- •4.1.1. Еволюція програми
- •4.1.2. Поняття рефакторингу
- •4.1.3. Ознаки того, що потрібен рефакторинг
- •4.1.4. Рівні рефакторингу
- •Приклади можливих кроків в процесі рефакторингу
- •4.1.5. Безпечний рефакторинг
- •4.1.6. Стратегії рефакторингу
- •4.2. Якість конструювання
- •4.2.1. Тестування коду розробником
- •Коли створювати тести
- •4.2.3. Переваги, які надає tdd
- •5. Практикум.
- •5.1. Рефакторинг в середовищі Eclipse.
- •5.2. Коректний та некоректний підхід - практичні приклади та зразки.
- •5.2.1. Використання іменованих констант.
- •5.2.3. Методи
- •5.2.4. Інкапсуляція
- •5.3. Створення програм у відповідності з принципами написання якісного коду.
- •5.4. Unit-тестування
- •5.5. Рефакторинг.
- •5.6. Система керування версіями Subversion (svn).
- •Література
3.2. Принципи використання змінних.
Грамотне оголошення змінних
Деякі мови програмування підтримують неявне оголошення змінних (наприклад, Visual Basic, JavaScript тощо), тобто якщо в програмі використовується ще не оголошена змінна, компілятор автоматично оголосить її.
Неявне оголошення змінних – дуже небезпечна можливість в мові програмування. Вона може привести до плутанини в змінних, імена яких схожі або до плутанини з типами даних. Наприклад:
Dim acctNum as Long
...
acctNo=85647
Мови програмування, які вимагають оголошення змінних, заставляють програміста бути більш уважним з даними своєї програми, що є однією з найбільших переваг такого роду мов. В мовах без обов’язкового оголошення змінних рекомендується включати в компіляторі опцію вимоги оголошення змінних, якщо така є (як наприклад у Visual Basic). Рекомендується оголошувати всі змінні, які планується використовувати, незалежно від того, чи вимагає це компілятор. Крім того, потрібно використовувати можливості середовища програмування по аналізу коду (наприклад, eclipse підкреслює жовтим змінні, які були оголошені, але не використані; по різному відображає поля і локальні змінні; виділяє всі використання змінної, на імені якої стоїть курсор).
Принципи ініціалізації змінних
Неправильна ініціалізація змінних є одним з найбільших джерел помилок в програмах. При неправильній ініціалізації проблеми пояснюються тим, що змінна може мати не те початкове значення, яке очікує програміст. Це може статись з однієї з причин:
Змінній не було присвоєно значення. Відповідно, вона має те випадкове значення, яке знаходилось у відповідних комірках пам’яті при запуску програми.
Значення змінної застаріло. Колись змінній було присвоєно значення, але воно втратило актуальність.
Одним частинам змінної були присвоєні значення, а іншим ні.
Для правильної ініціалізації рекомендовано дотримувати наступних основних принципів.
1) Ініціалізуйте кожну змінну при її оголошенні. Ініціалізація змінних при їх оголошенні – проста методика захисного програмування і хороша страховка від помилок ініціалізації.
2) Ініціалізуйте кожну змінну там, де вона використовується вперше. Visual Basic та деякі інші мови не дозволяють ініціалізувати змінну при її оголошенні. В результаті, код може набувати вигляду, коли змінні спочатку оголошуються, а потім ініціалізуються, і це відбувається далеко від місця першого використання даних змінних.
Приклад поганої ініціалізації змінних (Visual basic)
' оголошення всіх змінних
Dim accountIndex As Integer
Dim total As Double
Dim done As Boolean
' ініціалізація всіх змінних
accountIndex = 0
total =0.0
done = False
...
' використання змінної accountIndex
...
' використання змінної total
...
' використання змінної done
While Not done
...
Краще ініціалізувати кожну змінну ближче до місця її першого використання:
Dim accountIndex As Integer
accountIndex = 0
' використання змінної accountIndex
...
Dim total As Double
total = 0.0
'використання змінної total
...
Dim done As Boolean
done = False
' використання змінної done ...
While Not done
Другий варіант кращий за перший з декількох причин. Поки виконання першого прикладу дійде до фрагменту, де використовується змінна done, її значення може бути змінено. Навіть якщо при написанні програми це не так, не можна гарантувати, що це не відбудеться в майбутньому при зміні програми. Крім того, в першому прикладі всі змінні ініціалізуються в одному місці, через що складається враження, що всі вони використовуються впродовж всього методу, тоді як насправді змінна done використовується тільки в його кінці. Нарешті, в результаті внесення змін в програму, що дуже навіть може статись, код, що використовує змінну done, може опинитись в циклі, що вимагатиме ініціалізації змінної заново кожного разу. Бачимо, що перший приклад слабше захищений від помилок, ніж другий.
Ці два фрагменти ілюструють принцип близькості: групуйте пов’язані дії разом. Цей принцип стосується також близькості коментарів до коду, що вони описують, близькість коду налаштування циклу до самого циклу тощо.
3) В ідеальному випадку потрібно оголошувати та ініціалізовувати змінну безпосередньо перед першим зверненням до неї. Не всі мови це дозволяють.
4) По мірі можливості потрібно оголошувати змінні як final чи const.
5) Потрібно приділяти особливу увагу лічильникам та акумуляторам.
Змінні i, j, k, sum, total часто грають роль лічильників чи акумуляторів. Нерідко програмісти забувають задати нульове значення лічильнику чи акумулятору перед його черговим використанням.
6) Потрібно ініціалізувати поля класу в його конструкторі.
7) Потрібно уважно слідкувати за повідомленнями компілятора – він попереджає про не ініціалізовані змінні.
Одиничність мети кожної змінної
Потрібно використовувати кожну змінну з єдиною метою. Іноді виникає спокуса використати одну змінну в двох різних місцях для розв’язування двох різних задач. Зазвичай, таким змінним доводиться давати невдале ім’я у відповідності з однією з її цілей, або використовувати для обох задач тимчасову змінну (наприклад, temp). Наприклад:
temp = Sqrt( b*b - 4*a*c);
root[0] = ( -b + temp ) / ( 2 * a );
root[1] = ( -b - temp ) / ( 2 * a );
...
temp = root[0];
root[0] = root[1];
root[1] = temp;
Тут складається враження, що змінні temp в першій частині програми і в другій якось пов’язані між собою, хоча це не так. Краще було б написати так:
discriminant = Sqrt( b*b - 4*a*c);
root[0] = ( -b + temp ) / ( 2 * a );
root[1] = ( -b - temp ) / ( 2 * a );
...
oldRoot = root[0];
root[0] = root[1];
root[1] = temp;
Потрібно уникати змінних, що мають прихований сенс. Навіть якщо змінна з подвійним сенсом зрозуміла автору програми, вона може бути незрозумілою іншим. Наприклад, якщо додатне число в змінній bytesWritten означає число байтів, записаних у вихідний файл, а від’ємне – номер диску, на який здійснюється запис, то отримуємо так звану "гібридну зв’язність" ("hybrid coupling"), що є недоліком програми. Тому, в даному випадку доцільно використовувати для кожної величини окрему змінну.
Потрібно переконатись в тому, що використовуються всі оголошені змінні. Доведено, що невикористання оголошених змінних веде до збільшення кількості помилок. Компілятор java, наприклад, видає попередження про невикористані змінні.
Принципи вибору імен змінних
Найважливішим принципом іменування змінних полягає в тому, що ім’я повинно повністю і точно описувати сутність, яка представляється змінною, і, відповідно, легко читатись і легко запам’ятовуватись. Приклад:
Невдале іменування:
x= x-xx;
xxx= fido + SalesTax( fido );
x = x + LateFee(x1, x ) + xxx;
x = x + Interest(x1, x );
Вдале іменування:
balance = balance - lastPayment;
monthlyTotal = newPurchases + SalesTax(newPurchases);
balance = balance + LateFee(customerID, balance) + monthlyTotal;
balance = balance + Interest(customerID, balance);
Очевидно, що найкращим ім’ям є те, яке в словах повністю описує сенс змінної. Наприклад, змінну, що представляє число місць на стадіоні, можна назвати numberOfSeatsInTheStadium. Особливостями такого іменування є: позитивна – ім’я не потрібно розшифровувати, воно саме за себе говорить; негативна – ім’я занадто довге, щоб бути ефективним при практичному використанні.
Таблиця 1. Ефективні та неефективні імена
Сутність змінної |
Вдалі імена |
Невдалі імена |
Сума, на яку на даний момент виписано чеки |
runningTotal, checkTotal |
written, ct, checks, CHKTTL, x, xl, x2 |
Швидкість потягу |
velocity, trainVelocity, velocitylnMph |
velt, v, tv, x, xl,x2, train |
Поточна дата |
currentDate, todaysDate |
cd, current, c, x, xl, x2, date |
Число рядків на сторінці |
linesPerPage |
lpp, lines, l, x, xl, x2 |
Хороше мнемонічне ім’я описує зазвичай проблему, а не її вирішення. Наприклад, запис даних про співробітників можна назвати inputRecord або employeeData. Перше – комп’ютерний термін, що виражає ідею введення даних. Друге відноситься до предметної області, тому має перевагу.
Оптимальна довжина змінної лежить, мабуть, десь між довжинами змінних x та maximumNumberOfPointsInModernOlympics. Занадто короткі імена страждають від недостатнього вираження сенсу, в той час, як занадто довгі роблять складнішим набір програми і можуть зіпсувати візуальну структуру. Вважається, що оптимальною є довжина ім’я 10-16 символів. Але це не значить, що всі імена повинні бути такими – просто, якщо є більш короткі імена, потрібно приділити увагу тому, чи досить ясно вони описують сутність змінної.
Таблиця 2. Вдалі та невдалі імена з точки зору довжини
Занадто довгі імена |
numberOfPeopleOnTheUsOlympicTeam numberOfSeatsInTheStadium maximumNumberOfPointsInModernOlympics |
Занадто короткі імена |
n, np, ntm n, ns, nsisd m‚mp, max, points |
Те, що потрібно |
numTeamMembers, teamMemberCount numSeatsInStadium, seatCount teamPointsMax, pointsRecord |
Чи завжди короткі імена є невдалими? Ні, не завжди. Якщо змінній присвоюється коротке ім’я, наприклад, i, то сама довжина ім’я говорить про те, що змінна є другорядною і має обмежену область дії. Загалом, довгі імена краще присвоювати глобальним змінним, або таким, які рідко використовуються, а коротші – локальним змінним або змінним, що викликаються в циклах.
В багатьох програмах використовуються змінні, в яких зберігаються обчислені значення – суми, середні величини тощо. Доповнюючи ім’я специфікатором типу Total, Sum, Average, Max, Min, Record, String або Pointer, бажано ставити його в кінці. Такий підхід має декілька переваг. По-перше, найзначущіша частина ім’я стоїть на початку, перша читається і перша сприймається. По-друге, прийнявши дану конвенцію, можна уникнути плутанини, коли записуються семантично однакові імена синтаксично по-різному, наприклад, totalRevenue та revenueTotal. По-третє, імена типу revenueTotal, expenseTotal, revenueAverage, expenseAverage мають приємну для ока симетрію. Винятком з даного правила є позиція специфікатора Num. При розташуванні на початку ім’я специфікатор Num позначає загальне число, наприклад, numCustomers – загальне число замовників. Якщо ж він вказується в кінці ім’я, то позначає індекс: customerNum – номер поточного замовника. Іншою ознакою даної відмінності є буква s в кінці ідентифікатора numCustomers. Але навіть в цьому випадку специфікатор Num часто приводить до плутанини, тому краще замість нього застосовувати Count чи Total для позначення кількості та Index для позначення номера: customerCount, customerIndex.
