Вот список из 10 практик кодирования Java, которые более тонкие, чем эффективные правила Josh Bloch. По сравнению со списком Джоша Блоха, который легко выучить и сосредоточиться на повседневных ситуациях, этот список будет содержать ситуации, связанные с необычными вещами в дизайне API/SPI, что может оказать большое влияние.
Я столкнулся с ними во время написания и поддержания JOOQ (SQL моделировал внутренний DSL в Java). В качестве внутреннего DSL JOOQ бросает вызов Java Compilers и Generics в наибольшей степени, объединяя дженерики, изменяемые параметры и перегрузки, которые Джош Блох может не рекомендовать для такого широкого API.
Позвольте мне поделиться с вами 10 тонкими лучшими практиками для кодирования Java:
1. Помните деструктор C ++
Помните деструктор C ++? Не помни? Тогда вам действительно повезло, потому что вам не нужно отлаживать утечки памяти из -за памяти, выделенной после того, как удаление объекта не будет выпущено. Спасибо Sun/Oracle за реализацию механизма сбора мусора!
Тем не менее, деструктор предоставляет интересную функцию. Он понимает порядок обратного распределения, чтобы освободить память. Помните, что это так в Java, когда вы управляете синтаксисом деструктора класса:
Есть различные другие варианты использования. Вот конкретный пример того, как реализовать SPI некоторых слушателей событий:
@OverridePublic void перед EventEntext e) {super.beforeEvent (e); // Super -код перед моим кодом} @OverridePublic void Afterevent (EventContext e) {// Super -Code после моего кода Super.AfterEvent (e);} Печально известный философский ужин - еще один хороший пример того, почему это важно. Для вопросов о философской столовой, пожалуйста, проверьте ссылку:
http://adit.io/posts/2013-05-11-the-dining-philosophers-problem-with-ron-swanson.html
Правило : всякий раз, когда вы используете до/после, выделяйте/бесплатно, взять/вернуть семантику для реализации логики, рассмотрите, выполнять ли операции после/бесплатно/возврата в обратном порядке.
Предоставьте клиентам методы SPI, которые позволяют им легко вводить пользовательское поведение в вашу библиотеку/код. Остерегайтесь, что ваши суждения о эволюции SPI могут сбить вас с толку, заставив вас думать, что вы (не) намерены понадобиться дополнительные параметры. Конечно, функции не должны быть добавлены слишком рано. Но как только вы опубликуете свой SPI, после того, как вы решите следовать семантическим версиям, вы действительно пожалеете, добавив глупый однократный параметр в свой SPI, когда поймете, что в некоторых случаях вам может понадобиться другой параметр:
Interface EventListener {// Bad Void Message (String Message);}Что если вам нужен идентификатор сообщения и источник сообщения? Evolution API не позволит вам добавить параметры к вышеуказанным типам. Конечно, с Java 8 вы можете добавить метод защитника, который «защищает» ваши плохие дизайнерские решения в первые дни:
interface EventListener {// Bad Default void Message (String Message) {Message (сообщение, NULL, NULL); } // Лучше? void message (строковое сообщение, идентификатор целого числа, источник сообщений);} Обратите внимание, что, к сожалению, метод защитника не может использовать окончательный модификатор.
Но было бы гораздо лучше использовать контекстные объекты (или объекты параметров), чем загрязнять ваш SPI, используя множество методов.
Интерфейс MessageContext {String message (); Integer id (); Speakyource Source ();} интерфейс EventListener {// Awesome! void message (контекст MessageContext);} Вы можете развить API MessageContext API легче, чем EventListner SPI, потому что немногие пользователи будут реализовать его.
Правило : всякий раз, когда указан SPI, рассмотрите возможность использования объекта контекста/параметра вместо написания методов с фиксированными параметрами.
Примечание . Также хорошей идеей обмениваться результатами через выделенный тип Messageresult, который может быть построен с использованием API строителя. Это значительно увеличит гибкость эволюции SPI.
Программисты Swing обычно нажимают только несколько ярлыков, чтобы создать сотни или тысячи анонимных классов. В большинстве случаев не повредит это, пока следуют за интерфейсом, а жизненный цикл подтипа SPI не нарушается. Но не используйте анонимные, локальные или внутренние классы часто по простой причине - они сохранят ссылки на внешние классы. Потому что независимо от того, куда они идут, внешние категории должны следовать. Например, если внешняя работа локального класса является неправильной, весь график объекта будет претерпевать тонкие изменения, что может вызвать утечку памяти.
Правило: Перед написанием анонимных, локальных или внутренних классов, пожалуйста, подумайте о том, можете ли вы преобразовать его в статические или обычные классы верхнего уровня, избегая способов вернуть свои объекты в домен внешнего уровня.
Примечание. Используйте двойные вьющиеся скобки для инициализации простых объектов:
new Hashmap <String, String> () {{put ("1", "a"); PUT ("2", "B");}}Этот метод использует инициализатор экземпляра, описанный в спецификации JLS §8.6. Это выглядит хорошо на поверхности, но на самом деле это не рекомендуется. Потому что, если вы используете полностью независимый объект HashMap, экземпляр не всегда будет иметь ссылки на внешние объекты. Кроме того, это позволит загрузчику класса управлять большим количеством классов.
Темп Java8 приближается. С Java 8 приносит Lambda выражения, нравится вам это или нет. Хотя вашему API пользователям это может понравиться, вам лучше убедиться, что они могут использовать его как можно чаще. Поэтому, если ваш API не получает простые «скалярные» типы, такие как Int, Long, String и Date, пусть ваш API принимает SAM как можно чаще.
Что такое Сэм? SAM - это единственный абстрактный метод [тип]. Также известный как функциональный интерфейс, он скоро будет аннотирован как @functionalInterface. Это соответствует правилу 2, EventListener на самом деле SAM. Лучший SAM имеет только один параметр, так как это еще больше упростит написание выражений Lambda. Представьте себе, что пишете
слушатели.add (c -> system.out.println (c.message ()));
Заменить
alingerers.add (new EventListener () {@Override public void message (MessageContext c) {System.out.println (c.message ())); }});Представьте себе, что вы обращаетесь с XML в Joox. Joox содержит много Сэма:
$ (документ) // Найти элементы с идентификатором .find (c -> $ (c) .id ()! = null) // Найти их дети.
Правила: Будьте добры к пользователям API, начните писать интерфейсы SAM/FUNCTION.
Примечание: есть много интересных блогов о выражениях Java8 Lambda и улучшенных коллекциях API:
Я написал 1 или 2 статьи о java nulls, а также объяснил введение новых дополнительных классов в Java 8. С академической или практической точки зрения эти темы довольно интересны.
Хотя Null и NullPointerException все еще остаются недостатком на Java на данном этапе, вы все равно можете разработать API, у которого не будет никаких проблем. При разработке API -интерфейсов вы должны избегать возвращения NULL как можно больше, потому что ваши пользователи могут вызвать метод в цепочке:
initialize (omeargument) .calculate (data) .dispatch ();
Как видно из приведенного выше кода, ни один метод не должен вернуть NULL. Фактически, использование NULL обычно считается сопоставимой неоднородностью. Библиотека, такая как jQuery или Joox, полностью отказалась от NULL на итерационных объектах.
Нуль обычно используется в отсроченной инициализации. Во многих случаях следует избегать задержки инициализации без серьезного влияния на производительность. Фактически, если задействованная структура данных слишком велика, то задержка инициализация должна использоваться с осторожностью.
Правило: Методы должны избегать возврата нуля, когда они есть. NULL используется только для представления семантики «ненициализированного» или «не существовавшего».
Хотя в некоторых случаях можно вернуть метод с нулевым значением, никогда не возвращайте пустой массив или пустую коллекцию! Пожалуйста, смотрите метод java.io.file.list (), он разработан таким образом:
Этот метод возвращает массив строк для всех файлов или каталогов в указанном каталоге. Если каталог пуст, возвращаемый массив пуст. Верните NULL, если указанный путь не существует или возникает ошибка ввода/вывода.
Следовательно, этот метод обычно используется так:
File Directory = // ... if (directory.isdirectory ()) {string [] list = directory.list (); if (list! = null) {for (string file: list) {// ...}}}Как вы думаете, нуждающаяся проверка необходима? Большинство операций ввода/вывода будут создавать ioexceptions, но этот метод возвращает только NULL. NULL не может хранить сообщения об ошибках ввода/вывода. Следовательно, такой дизайн имеет следующие три недостатки:
Если вы посмотрите на проблему с точки зрения коллекций, то пустой массив или коллекция - лучшая реализация «не было». Возвращение пустого массива или сбора имеет мало практического значения, если не используется для отсроченной инициализации.
Правило: возвращенный массив или коллекция не должны быть нулевыми.
Преимущество HTTP - без сохранения состояния. Все соответствующие состояния передаются в каждом запросе и ответе. Это суть именования отдыха: государственная передача (репрезентативное государственное перенос). Также здорово сделать это на Java. Подумайте об этом с точки зрения правила 2, когда метод получает объект параметра состояния. Если состояние передается через такой объект, а не управляет государством снаружи, все будет проще. Возьмите JDBC в качестве примера. В следующем примере гласит курсор из хранимой программы.
CallableStatement s = connection.preparecall ("{? = ...}"); // условно -манипулирование состоянием утверждения: s.registeroutparameter (1, cursor); S.SetString (2, "ABC"); S.Execute (); ResultSet rs = s.getObject (1); // verbose manipulation let stect: rsnext (1);Это делает JDBC API таким странным. Каждый объект является государственным и трудным для эксплуатации. В частности, есть две основные проблемы:
Правила: больше реализаций в функциональном стиле. Состояние передачи через параметры метода. Очень мало состояний операционного объекта.
Это относительно простой способ работы. В относительно сложных объектных системах вы можете добиться значительных улучшений производительности, если вы впервые выносите равные суждения в методе evallos () всех объектов:
@OverridePublic boolean equals (объект другой) {if (this == Другое) вернуть true; // другая логика суждения равенства ...}Обратите внимание, что другие проверки короткого замыкания могут включать проверки нулевых значений, поэтому их также следует добавить:
@OverridePublic boolean equals (объект другой) {if (this == Другое) вернуть true; if (другой == null) вернуть false; // Остальная логика равенства ...}Правило: Используйте короткие замыкания во всех ваших методах Equals () для повышения производительности.
Некоторые люди могут не согласиться с этим, потому что сделать метод по умолчанию в конечном итоге противоречит привычке разработчиков Java. Но если у вас есть полный контроль над кодом, это, безусловно, правильно сделать метод по умолчанию в конечном итоге:
Это особенно подходит для статических методов, в этом случае «наложение» (на самом деле окклюзия) едва ли работает. Недавно я наткнулся на пример очень плохого статического метода затенения в Apache Tika. Взгляните:
TikainputStream Extends TaggedInputStream для маскировки своего статического метода get () с относительно другой реализацией.
В отличие от традиционных методов, статические методы не могут быть перезаписаны друг с другом, потому что вызов связан с статическим вызовом метода во время компиляции. Если вам не повезло, вы можете случайно получить неправильный метод.
Правило: Если у вас есть полный контроль над вашим API, сделайте как можно больше методов по умолчанию в конечном итоге.
В особых случаях вы можете использовать метод переменной переменной «принять все» для получения объекта ... Параметр:
void accectall (объект ... все);
Написание такого метода приносит небольшое ощущение JavaScript в экосистему Java. Конечно, вы можете ограничить фактический тип на основе фактической ситуации, такой как строка…. Поскольку вы не хотите ограничивать слишком много, вы можете подумать, что это хорошая идея, чтобы заменить объект на общий T:
void accectall (t ... все);
Но нет. T всегда будет выведен в качестве объекта. На самом деле, вы можете просто подумать, что дженерики не могут быть использованы в приведенных выше методах. Что еще более важно, вы можете подумать, что можете перегрузить приведенный выше метод, но вы не можете:
void accectall (t ... all); void accectall (строковое сообщение, t ... все);
Похоже, вы можете при желании передать строковое сообщение методу. Но что происходит с этим звонком?
accectall ("Сообщение", 123, "ABC");Компилятор делает T как <? Расширяет сериализуемые и сопоставимые <? >>, что сделает вызов неясным!
Поэтому всякий раз, когда у вас есть подпись «принять все» (даже если она общая), вы никогда не сможете перегружать ее. Потребители API могут только позволить компилятору «случайно» выбирать «правильный» метод, когда им повезло. Но также возможно использовать метод приема и не вызывать какой-либо метод.
Правило : избегайте подписи «принять все», если это возможно. Если нет, не перегружайте такой метод.
Ява - зверь. В отличие от других более идеалистических языков, он медленно развивался до того, что он является сегодня. Это может быть хорошо, потому что со скоростью развития Java уже есть сотни предупреждений, и эти предупреждения могут быть охвачены только с помощью многолетнего опыта.
Следите за обновлениями, чтобы получить больше десяти лучших списков по этой теме!
Оригинальная ссылка: JOOQ Перевод: ImportNew.com - Liken
Ссылка на перевод: http://www.importnew.com/10138.html
[Пожалуйста, сохраните исходный источник, переводчик и ссылку на перевод при перепечатку. ]