Як писати методи ефективно (переклад статті)
Ця замітка - частина курсу Advanced Java (Просунутий Java.) Нашої академії
Цей курс створений, щоб допомогти вам зробити використання Java більш ефективним. Тут обговорюються більш складні теми, як створення об'єктів, розпаралелювання, сериализация, рефлексія і багато іншого. Ці знання будуть гідом для вашої подорожі до вершин майстерності Java.
Зміст курсу
1. Введення
2. Сигнатура методів
3. Тіло методу
4. Перевантаження методу
5. Перевизначення методу
6. Вбудовування
7. Рекурсія
8. Посилання методу
9. Незмінність
10. Документування методу
11. Параметри методу і повертаються значення
12. Метод як точка входу в додаток
13. Що далі
14. Завантаження вихідного коду
1. Введення
В цьому розділі підручника ми збираємося витратити деякий час на обговорення різних аспектів, пов'язаних з проектуванням і реалізацією методів в Java. У попередній частині підручника можна було переконатися, що написати методи на Java - дуже просто, проте є багато речей, які можуть зробити ваші методи більш чіткими і ефективними.
2. сигнатури методів
Як ви вже знаєте, Java - це об'єктно-орієнтована мова. По суті, кожен метод Java відноситься до якоїсь частини класу (або до самого класу в разі статистичного методу). Він має правила видимості (або доступності), може бути оголошений абстрактним або фінальним і так далі. Однак можливо найбільш важлива частина методу - це його сигнатура: тип значення і аргументів, плюс список перевіряються винятків реалізації кожного методу, який може бути викинутий (але ця частина раніше використовувалася не часто, і ще менш часто в наші дні). Почнемо з маленького прикладу.
Метод main приймає масив рядків тільки як аргумент args і нічого не повертає. Це могло б бути дуже приємно - робити все методи такими простими як main. Але в реальності сигнатура методу може стати нечитаною. Давайте поглянемо на наступний приклад:
Перше що тут помітно, що умовні позначення спочатку використовуються в назвах методів Java, наприклад setTitleVisible.Імя добре підібрано і намагається описати, що в методі покладається зробити.
Друге, імена аргументів говорять (або принаймні натякають) щодо їх мети. Це дуже важливо знайти правильні, розумні імена для аргументів методу, замість int i, String s, boolean f (в дуже рідкісних випадках це, однак, має сенс).
Третє, метод має тільки три аргументи. Хоча Java має набагато більший межа дозволеного числа аргументів, настійно рекомендовано не перевищувати кількість аргументів більше 6. Вихід за ці рамки робить сигнатуру важко розуміється.
З тих пір як була випущена Java 5, методи можуть мати різний список аргументів однакового типу (названий varargs - змінні аргументи) і використовувати спеціальний синтаксис, наприклад:
Наступний приклад демонструє використання перевірки винятків як частини сигнатури методу. В недалекому минулому перевірка виключень показала себе не настільки корисною, якою вона передбачалася бути, в результаті шаблонний код був використаний скоріше для запису, ніж для вирішення проблем.
3. Тіло методу
Кожен метод має свою реалізацію і мета існування. Однак, є пара загальних рекомендацій які реально допомагають написання ясних і зрозумілих методів.
Ймовірно, найбільш важливий принцип - це принцип одиничної відповідальності: треба намагатися реалізувати метод таким шляхом, щоб кожен одиничний метод робив щось одне, і робив це добре. Дотримуючись цього принципу можливо роздування кількості методів класу, і важливо знайти правильний баланс.
Інша важлива річ в процесі кодування і проектування - це робити реалізовані методи короткими. Для коротких методів легко зрозуміти причину, по якій вони зроблені, плюс вони звичайно уміщаються на екран, і таким чином можуть бути дуже швидко зрозуміли читачем вашого коду.
Останній по порядку (але не за значенням) рада пов'язаний з використанням return операторів. Якщо метод повертає деяке значення, намагайтеся мінімізувати число місць, де return значення було б викликано (деякі люди йдуть навіть далі і рекомендують використовувати лише одиничне return значення у всіх випадках. Чим більше return значень має метод, тим важче стає слідувати його логіці і модифікувати (або оптимізувати) реалізацію.
4. Перевантаження методу
Техніка перевантаження методів часто використовується, щоб забезпечити спеціалізацію версій методу для різних типів аргументів або їх комбінацій. Хоча ім'я методу однакове комп'ютер вибирає правильну альтернативу, заглиблюючись в поточні значення аргументів в точці виклику (кращий приклад перевантаження це конструктори Java: ім'я завжди однакове, але аргументи різні) або викликає помилку компілятора, якщо такий варіант методу не знайдений. наприклад:
Перевантаження методу почасти близька до дженерика (більше інформації про дженериках можна знайти в частині 4 підручника How and when to use Generics (Як і коли використовувати дженерики)), проте перевантаження використовується в разі, де підхід з використанням дженериків не працює добре і кожен або більшість типів аргументів, які є дженериками, вимагають своїх власних спеціалізованих реалізацій. Проте, комбінуючи обидва способи дженерики і перевантаження можна бути дуже продуктивним, але часто це неможливо в Java, тому що тип стирається (більше інформації в частині 4 підручника How and when to use Generics (Як і коли використовувати дженерики)). Давайте поглянемо на приклад:
Хоча цей шматок коду міг бути написаний без використання дженериків, це не має значення для наших демонстраційних цілей. Цікаво, що метод numberToString перевантажений спеціальної реалізацією BigDecimal і версія на дженериках призначена для всіх інших чисел.
5. Перевизначення методу
Ми багато говорили про перевизначення методів в частині 3 підручника (How to design Classes and Interfaces (Як проектувати класи і інтерфейси). У цьому розділі, коли ми вже знаємо про перевантаження методів, ми збираємося показати, чому використання @Override анотації так важливо. Наш приклад продемонструє тонка різниця між перевизначенням методу і перевантаженням методу в простій ієрархії класів.
Батьківський клас має тільки один метод toObject. Давайте створимо підклас цього класу і спробуємо придумати версію методу перетворення чисел в рядки (замість необроблених об'єктів).
Проте, сигнатура методу toObject в дочірньому класі небагато чим відрізняється (див Covariant method return types (коваріантного типи повертаються методами) для більш докладної інформації), і це робить перевизначення його з суперкласу в свій клас, при цьому компілятор Java не видає ніяких помилок і попереджень. Тепер, давайте додамо ще один метод до дочірнього класу.
Знову ж таки, є тільки невелика різниця в сигнатурі методу (Double замість Number), але те, що в даному випадку це перевантажена версія методу, не скасовує перевизначення методу батька. Тобто, коли підказка від компілятора Java і @Override анотації перекриваються: метод з анотацією з останнього прикладу з @Override викличе помилку компілятора.
6. Вбудовування
Вбудовування - це оптимізація, здійснювана за допомогою Java JIT (точно в строк) компілятора для того, щоб усунути конкретний виклик методу і замінити його безпосередньо реалізацією методу. Використання компілятора JIT евристики залежить від двох речей - як часто метод викликається в даний час, а також від того, наскільки він великий. Методи, які занадто великі, не можуть бути ефективно вбудовані. Вбудовування може забезпечити значний приріст продуктивності коду і перевага зберігання методів короткими, як ми вже обговорювали в розділі Method body (Тіло методу).
7. Рекурсія
Рекурсія в Java - це техніка, де метод викликає сам себе, виконуючи розрахунки. Наприклад, давайте поглянемо на наступний приклад, який підсумовує число масиву:
Це дуже неефективна реалізація, однак вона демонструє рекурсию досить добре. Існує одна добре відома проблема з рекурсивними методами: в залежності, наскільки глибока ланцюг викликів, вони можуть переповнити стек і викликати виключення StackOverflowError. Але не все так погано, як здається, тому що є техніка, яка може усунути переповнення стека, звана tail call optimization (оптимізація хвоста виклику). Вона може бути застосована, якщо метод з хвостової рекурсією (методи з хвостової рекурсією це методи, в яких всі рекурсивні виклики це хвостові виклики). Наприклад, давайте перепишемо попередній алгоритм з використанням в хвостовій рекурсії:
На жаль, на даний момент компілятор Java (а також компілятор JVM JIT) не підтримує tail call optimization хвостову оптимізація, але все-таки це дуже корисна техніка, і її треба знати і брати до уваги, коли ви пишете рекурсивні алгоритми в Java.
8. Посилання методів
В Java 8 зроблений величезний крок вперед, шляхом введення функціональних понять в мову Java. Підстава, яке трактує методи як дані, поняття, яке підтримувалося в мові до цього (проте, з тих пір як випущена Java 7, JVM і стандартна бібліотека Java вже були деякі напрацювання, щоб зробити це можливим). На щастя, маючи посилання методів, тепер це можливо.
Давайте поглянемо на невеличкий приклад того, як методи можуть бути використані в якості аргументів інших методів.
В останньому рядку main метод використовує посилання на println метод щоб надрукувати кожен елемент з колекції рядків в консоль, він приймає в якості аргументу іншого методу, forEach.
9. Незмінність
Кожен виклик LocalDateTime об'єкта, який повинен змінити свій стан повертає новий екземпляр LocalDateTime, і тримає оригінал без змін. Це велике зрушення в парадигмі дизайну API в порівнянні з старими Calendar і Date, (які, м'яко кажучи, були не дуже приємні у використанні і викликали багато головного болю).
10. Документування методу
Це досить багатослівна документація для такого простого методу як parse, але це показує пару корисних можливостей забезпечуваних інструментом Javadoc tool, в тому числі посилання на інші класи, зразки фрагментів і просунутого форматування. Ось як цей документація методів відбивається в Eclipse, однією з популярних Java IDE.
Просто дивлячись на зображення вище, будь-який розробник Java від молодшого до старшого рівня може зрозуміти мету методу і належним чином використовувати її.
11. Параметри методу і повертаються значення
Документування ваших методів - це велика річ, але, на жаль, це не попереджає випадки, коли метод називають, використовуючи неправильні або несподівані значення аргументів. Через це, як правило, всі публічні методи повинні підтвердити свої аргументи і ніколи не повинні бути впевнені, що весь час при виклику будуть вказані правильні значення (патерн більш відомий як sanity checks (санітарна перевірка)).
Повертаючись до нашого прикладу з попереднього розділу, метод parse повинен виконати перевірку свого єдиного аргументу, перш ніж робити що-небудь з ним:
Java має інший варіант виконання перевірки і sanity checks, використовуючи assert оператори. Однак, ті, які могли бути вимкнені під час виконання і можуть бути не виконані. Переважно, завжди виконувати такі перевірки і викликати відповідні винятки.
Навіть маючи документовані методи і перевірку їх аргументів, хочу зробити ще пару зауважень пов'язаних з повертаються значеннями. До того як вийшла Java 8, найпростішим способом сказати що метод в даний час не має значення щоб його повернути було просто повернути нуль. Ось чому Java так погано отримати виняток NullPointerException. Java 8 намагається вирішити це питання з введенням Optional class. Давайте поглянемо на цей приклад:
Optional надає багато корисних методів, і повністю усуває необхідність повертати в методі null і забруднювати всюди ваш код перевірками на null. Єдиний виняток, ймовірно, це колекції. Всякий раз, коли метод повертає колекцію, завжди краще повернути null замість null (і навіть Optional), наприклад:
12. Метод як точка входу в додаток
Навіть якщо ви простий розробник пише програми у вашій організації або учасник в одній з найпопулярніших Java framework or library, проектні рішення, які ви приймаєте, грають дуже важливу роль в тому, як ваш код буде використовуватися.
У той час як методичні рекомендації проектування API стоять декількох книг, ця частина підручника стосується багатьох з них (як методи стають точкою входу в API), таким чином, короткий огляд буде дуже корисний:
• Використовуйте осмислені імена для методів і їх аргументів (Method signatures)
Намагайтеся, щоб кількість аргументів, щоб бути менше 6-ти (розділ Method signatures)
• Зберігайте ваші методи короткими і читабельними (розділ Method body і Inlining)
• Завжди документуйте відкриті методи, в тому числі попередніх умов і приклади, якщо це має сенс (розділ Method Documentation)
• Завжди виконуйте перевірку аргументів і sanity checks (section Method Parameters and Return Values)
• Намагайтеся уникнути null, як повертається значення (розділ Method Parameters and Return Values)
• Кожного разу, коли це має сенс, спробуйте проектувати незмінні методи (які не впливають на внутрішній стан, розділ Immutability)
• Використовуйте правила видимості і доступності, щоб приховати методи, які не повинні бути публічними (частина 3 підручника, How to design Classes and Interfaces)
13. Що далі
Ця частина підручника говорить трохи менше про Java як про мову, але більше про те, як використовувати мову Java ефективно, зокрема, написання читаються, чистих, задокументовані і ефективних методів. У наступному розділі ми будемо продовжувати ту ж основну ідею і обговорювати загальні принципи програмування, які призначені, щоб допомогти вам як розробнику Java стати краще.
14. Завантаження вихідного коду
Це був урок був присвячений тому, як ефективно писати методи. Ви можете завантажити вихідний код тут: advanced-java-part-6