JavaRush /Blog Java /Random-ES /Cómo escribir métodos de forma eficaz (traducción del art...
pena
Nivel 41
Москва

Cómo escribir métodos de forma eficaz (traducción del artículo)

Publicado en el grupo Random-ES
El artículo original está en: http://www.javacodegeeks.com/2015/09/how-to-write-methods- Effectively.html#download tutorial Publicado por: Andrey Redko (Andrey Redko) en Core Java (Java Core) 18 de septiembre de 2015 Esta nota es parte del curso Java avanzado de nuestra Academia. Este curso está diseñado para ayudarlo a utilizar Java de manera más efectiva. Aquí se analizan temas más avanzados, como la creación de objetos, paralelización, serialización, reflexión y mucho más. Este conocimiento guiará su viaje hacia las alturas del dominio de Java.
Contenidos del curso
1. Introducción 2. Firma del método 3. Cuerpo del método 4. Sobrecarga del método 5. Anulación del método 6. Inlining 7. Recursión 8. Referencias del método 9. Inmutabilidad 10. Documentación del método 11. Parámetros del método y valores de retorno 12. Método como punto de entrada al Apéndice 13. Qué sigue 14. Descarga del código fuente
1. Introducción
En esta sección del tutorial, dedicaremos algún tiempo a discutir varios aspectos relacionados con el diseño e implementación de métodos en Java. En la parte anterior del tutorial, pudiste ver que escribir métodos en Java es muy fácil, pero hay muchas cosas que pueden hacer que tus métodos sean más legibles y eficientes.
2. Firmas de métodos
Como ya sabes, Java es un lenguaje orientado a objetos. Esencialmente, cada método Java pertenece a alguna parte de una clase (o a la clase misma en el caso de un método estadístico). Tiene reglas de visibilidad (o accesibilidad), puede declararse abstracto o final, etc. Sin embargo, quizás la parte más importante de un método es su firma: el tipo de retorno y los argumentos, además de la lista de excepciones comprobadas de la implementación de cada método que se pueden generar (pero esta parte no se usaba con frecuencia en el pasado, y aún menos en estos días). ). Empecemos con un pequeño ejemplo. 1 public static void main( String[] args ) { 2 // Some implementation here 3 } El método principal toma una matriz de cadenas solo como argumento de argumentos y no devuelve nada. Sería muy bueno hacer que todos los métodos sean tan simples como el principal. Pero en realidad, la firma del método puede volverse ilegible. Veamos el siguiente ejemplo: 1 public void setTitleVisible( int lenght, String title, boolean visible ) { 2 // Some implementation here 3 } Lo primero que notará aquí es que las convenciones se usan de forma nativa en los nombres de los métodos Java, como setTitleVisible. El nombre está bien elegido e intenta describir lo que se supone que debe hacer el método. En segundo lugar, los nombres de los argumentos dicen (o al menos insinúan) cuál es su propósito. Es muy importante encontrar nombres correctos y significativos para los argumentos del método, en lugar de int i, String s, boolean f (sin embargo, en casos muy raros esto tiene sentido). En tercer lugar, el método tiene sólo tres argumentos. Aunque Java tiene un límite mucho más alto en la cantidad de argumentos permitidos, se recomienda encarecidamente no exceder la cantidad de argumentos mayor que 6. Ir más allá de este límite hace que la firma sea difícil de entender. Desde que se lanzó Java 5, los métodos pueden tener diferentes listas de argumentos del mismo tipo (llamados varargs) y usar una sintaxis especial, como por ejemplo: 1 public void find( String … elements ) { 2 // Some implementation here 3 } Internamente, el compilador de Java convierte argumentos variables en una matriz de los tipos apropiados y, por lo tanto, los argumentos variables pueden ser adoptado para implementar el método. Curiosamente, Java también te permite declarar varargs usando parámetros genéricos. Sin embargo, debido a que se desconoce el tipo de argumento, el compilador de Java quiere asegurarse de que varargs se usen correctamente y recomienda anotar los métodos finales con @SafeVarargs (para obtener más información, consulte la Parte 5 del tutorial, Cómo y cuándo usar Enumeraciones y Anotaciones). Usamos Enumeraciones y Comentarios) Por ejemplo: 1 @SafeVarargs 2 final public< T > void find( T ... elements ) { 3 // Some implementation here 4 } Другой ближайший путь это использовать @SuppressWarnings комментарии, например 1 @SuppressWarnings( "unchecked" ) 2 public< T > void findSuppressed( T ... elements ) { 3 // Some implementation here 4 } Следующий пример демонстрирует использование проверки исключений Cómo части сигнатуры метода. В недалеком прошлом проверка исключений показала себя не настолько полезной, Cómoой она предполагалась быть, в результате шаблонный código был использован скорее для записи, чем для решения проблем. 1 public void write( File file ) throws IOException { 2 // Some implementation here 3 } Последнее, но, тем не менее, важное, Cómo правило, рекомендуется (но редко используется), отметить аргументы метода, Cómo final. Это поможет избавиться от практики написания плохого códigoа, когда аргументы метода предназначены различным значениям. Кроме того, такие аргументы метода могут быть использованы анонимными классами (подробнее об анонимных классов рассматривается в части 3 учебника, , How to design Classes and Interfaces (Как проектировать Классы и Интерфейсы)), хотя Java 8 облегчила немного это ограничение путем введения эффективных final переменных.
3. Тело метода
Каждый метод имеет свою реализацию и цель существования. Однако, имеется пара общих рекомендаций которые реально помогают написанию ясных и понятных методов. Вероятно, наиболее важный принцип - это принцип единичной ответственности: нужно пытаться реализовать метод таким путем, чтобы каждый единичный метод делал что-то одно, и делал это хорошо. Следуя этому принципу возможно раздувание количества методов класса, и важно найти правильный баланс. Другая важная вещь в процессе códigoирования и проектирования - это делать реализуемые методы короткими. Для коротких методов легко понять причину, по которой они сделаны, плюс они обычно умещаются на экран, и таким образом могут быть очень быстро поняты читателем вашего códigoа. Последний по порядку (но не по значению) совет связан с использованием return операторов. Если метод возвращает некоторое significado, пытайтесь минимизировать число мест, где return significado было бы вызвано (некоторые люди идут даже дальше и рекомендуют использовать лишь единичное return significado во всех случаях. Чем больше return значений имеет метод, тем более тяжело становится следовать его логике и модифицировать (o оптимизировать) реализацию.
4. Перегрузка метода
Техника перегрузки методов часто используется, чтобы обеспечить специализацию версий метода для различных типов аргументов o их комбинаций. Хотя Nombre метода одинаковое компьютер выбирает правильную альтернативу, углубляясь в текущие значения аргументов в точке вызова (лучший пример перегрузки это конструкторы Java: Nombre всегда одинаковое, но аргументы разные) o вызывает ошибку компилятора, если такой вариант метода не найден. Например: 1 public String numberToString( Long number ) { 2 return Long.toString( number ); 3 } 4 5 public String numberToString( BigDecimal number ) { 6 return number.toString(); 7 } Перегрузка метода отчасти близка к дженерикам (больше информации о дженериках можно найти в части 4 учебника How and when to use Generics (Как и когда использовать дженерики)), однако перегрузка используется в случае, где подход с использованием дженериков не работает хорошо и каждый o большинство типов аргументов, которые являются дженериками, требуют своих собственных специализированных реализаций. Тем не менее, комбинируя оба способа дженерики и перегрузку можно быть очень производительным, но часто это невозможно в Java, потому что тип стирается (больше информации в части 4 учебника How and when to use Generics (Как и когда использовать дженерики)). Давайте взглянем на пример: 1 public< T extends Number > String numberToString( T number ) { 2 return number.toString(); 3 } 4 5 public String numberToString( BigDecimal number ) { 6 return number.toPlainString(); 7 } Хотя этот кусок códigoа мог быть написан без использования дженериков, это неважно для наших демонстрационных целей. Интересно, что метод numberToString перегружен специальной реализацией BigDecimal и versión на дженериках предназначена для всех остальных чисел.
5. Переопределение метода
Мы много говорo о переопределении методов в части 3 учебника (How to design Classes and Interfaces (Как проектировать классы и интерфейсы). В этом разделе, когда мы уже знаем о перегрузке методов, мы собираемся показать, почему использование @Override аннотации так важно. Наш пример продемонстрирует тонкое различие между переопределением метода и перегрузкой метода в простой иерархии классов. 1 public class Parent { 2 public Object toObject( Number number ) { 3 return number.toString(); 4 } 5 } Родительский класс имеет только один метод toObject. Давайте создадим подкласс этого класса и попытаемся придумать версию метода преобразования чисел в строки (en lugar de необработанных un objetoов). 1 public class Child extends Parent { 2 @Override 3 public String toObject( Number number ) { 4 return number.toString(); 5 } 6 } Тем не менее, сигнатура метода toObject в дочернем классе немногим отличается (см Covariant method return types (Ковариантные типы возвращаемые методами) для более подробной информации), и это делает переопределение его из суперкласса в свой класс, при этом компилятор Java не выдает ниCómoих ошибок и предупреждений. Теперь, давайте добавим еще один метод к дочернему классу. 1 public class Child extends Parent { 2 public String toObject( Double number ) { 3 return number.toString(); 4 } 5 } Опять же, есть только небольшая разница в сигнатуре метода (Double en lugar de Number), но то, что в данном случае это перегруженная versión метода, не отменяет переопределения метода родителя. То есть, когда подсказка от компилятора Java и @Override аннотации перекрываются: метод с аннотацией из последнего примера с @Override вызовет ошибку компилятора.
6. Встраивание
Встраивание - это оптимизация, осуществляемая с помощью Java JIT (точно в срок) компилятора для того, чтобы устранить конкретный вызов метода и заменить его непосредственно реализацией метода. Использование компилятора JIT эвристики зависит от двух вещей - Cómo часто метод вызывается в настоящее время, а также от того, насколько он большой. Методы, которые слишком велики, не могут быть эффективно встроены. Встраивание может обеспечить значительный прирост производительности códigoа и преимущество хранения методов короткими, Cómo мы уже обсуждали в разделе Method body (Тело метода).
7. Рекурсия
Рекурсия в Java - это техника, где метод вызывает сам себя, выполняя расчеты. Например, давайте взглянем на следующий пример, который суммирует число массива: 1 public int sum( int[] numbers ) { 2 if( numbers.length == 0 ) { 3 return 0; 4 } if( numbers.length == 1 ) { 5 return numbers[ 0 ]; 6 } else { 7 return numbers[ 0 ] + sum( Arrays.copyOfRange( numbers, 1, numbers.length ) ); 8 } 9 } Это очень неэффективная реализация, однако она демонстрирует рекурсию достаточно хорошо. Существует одна хорошо известная проблема с рекурсивными методами: в зависимости, насколько глубока цепь вызовов, они могут переполнить стек и вызвать исключение StackOverflowError. Но не все так плохо, Cómo кажется, потому что есть техника, которая может устранить переполнение стека, называемая tail call optimization (оптимизация хвоста вызова). Она может быть применена, если метод с хвостовой рекурсией (методы с хвостовой рекурсией это методы, в которых все рекурсивные вызовы это хвостовые вызовы). Например, давайте перепишем предыдущий алгоритм с использованием в хвостовой рекурсии: 01 public int sum( int initial, int[] numbers ) { 02 if( numbers.length == 0 ) { 03 return initial; 04 } if( numbers.length == 1 ) { 05 return initial + numbers[ 0 ]; 06 } else { 07 return sum( initial + numbers[ 0 ], 08 Arrays.copyOfRange( numbers, 1, numbers.length ) ); 09 } 10 } К сожалению, на данный момент компилятор Java (а также компилятор JVM JIT) не поддерживает tail call optimization хвостовую оптимизация, но все-таки это очень полезная техника, и ее надо знать и принимать во внимание, когда вы пишете рекурсивные алгоритмы в Java.
8. Ссылки методов
В Java 8 сделан огромный шаг вперед, путем введения функциональных понятий в язык Java. Основание, которое трактует методы Cómo данные, понятие, которое не поддерживалось в языке до этого (однако, с тех пор Cómo выпущена Java 7, JVM и стандартная библиотека Java уже были некоторые наработки, чтобы сделать это возможным). К счастью, имея ссылки методов, теперь это возможно. Ссылка статического метода: SomeClass::staticMethodName Ссылка на метод экземпляра конкретного un objetoа: someInstance::instanceMethodName Ссылка на метод экземпляра произвольного un objetoа определенного типа: SomeType::methodName Ссылка на конструктор: SomeClass::new Давайте взглянем на небольшой пример того, Cómo методы могут быть использованы в качестве аргументов других методов. 01 public class MethodReference { 02 public static void println( String s ) { 03 System.out.println( s ); 04 } 05 06 public static void main( String[] args ) { 07 final Collection< String > strings = Arrays.asList( "s1", "s2", "s3" ); 08 strings.stream().forEach( MethodReference::println ); 09 } 10 } В последней строке main метод использует ссылку на println метод чтобы напечатать каждый элемент из коллекции строк в консоль, он передается в качестве аргумента другому методу, forEach.
9. Неизменность
Неизменность обращает на себя много внимания в эти дни, и Java не является исключением. Хорошо известно, что неизменности трудно добиться в Java, но это не значит, что это должно быть проигнорировано. В Java, неизменность - это все знания об изменении внутреннего состояния. В качестве примера, давайте взглянем на спецификации JavaBeans (http://docs.oracle.com/javase/tutorial/javabeans/). В ней говорится, очень ясно, что сеттеры могут изменить состояние un objetoа, что- то до этого содержащего, и это то, что ожидает каждый разработчик Java. Тем не менее, альтернативный подход мог бы не менять состояние, а возвращать новый un objeto (new) каждый раз. Это не так страшно, Cómo кажется, и новый Java 8 Date/Time API ( разработан под JSR 310: Date and Time API прикрытием) является отличным примером этого. Давайте взглянем на следующий фрагмент códigoа: 1 final LocalDateTime now = LocalDateTime.now(); 2 final LocalDateTime tomorrow = now.plusHours( 24 ); 3 4 final LocalDateTime midnight = now 5 .withHour( 0 ) 6 .withMinute( 0 ) 7 .withSecond( 0 ) 8 .withNano( 0 ); Каждый вызов LocalDateTime un objetoа, который должен изменить свое состояние возвращает новый экземпляр LocalDateTime, и держит оригинал без изменений. Это большой сдвиг в парадигме дизайна API по сравнению с старыми Calendar и Date, (которые, мягко говоря, были не очень приятны в использовании и вызвали много головной боли).
10. Документирование метода
В Java, в частности, если вы разрабатываете Cómoую-то библиотеку o framework, все публичные методы должны быть заdocumentoированы с помощью инструмента Javadoc (http://www.oracle.com/technetwork/articles/java/index-jsp-135444.html). Строго говоря, ничего не заставляет вас делать это, но хорошая documentoация помогает другим разработчикам понять, что конкретный метод делает, Cómoие аргументы он требует, Cómoовы предположения o ограничения его реализации, Cómoие типы исключений он вызывает и когда они возникают, Cómoое может быть возвращаемое significado (если таковые имеются), а также многие другие вещи. Давайте взглянем на следующий пример: 01 /** 02 * The method parses the string argument as a signed decimal integer. 03 * The characters in the string must all be decimal digits, except 04 * that the first character may be a minus sign {@code '-'} or plus 05 * sign {@code '+'}. 06 * 07 *

An exception of type {@code NumberFormatException} is thrown if 08 * string is {@code null} or has length of zero. 09 * 10 *

Examples: 11 *

12	 * parse( "0" ) returns 0
13	 * parse( "+42") returns 42
14	 * parse( "-2" ) returns -2
15	 * parse( "string" ) throws a NumberFormatException
16	 * 
17 * 18 * @param str a {@code String} containing the {@code int} representation to be parsed 19 * @return the integer value represented by the string 20 * @exception NumberFormatException if the string does not contain a valid integer value 21 */ 22 public int parse( String str ) throws NumberFormatException { 23 return Integer.parseInt( str ); 24 }
Это довольно многословная documentoация для такого простого метода Cómo parse, но это показывает пару полезных возможностей обеспечиваемых инструментом Javadoc tool, в том числе ссылки на другие классы, образцы фрагментов и продвинутого форматирования. Вот Cómo этот documentoация методов отражается в Eclipse, одной из популярных Java IDE. Просто глядя на изображение выше, любой разработчик Java от младшего до старшего уровня может понять цель метода и надлежащим образом использовать ее.
11. Параметры метода и возвращаемые значения
Документирование ваших методов - это великая вещь, но, к сожалению, это не предупреждает случаи, когда метод называют, используя неправильные o неожиданные значения аргументов. Из-за этого, Cómo правило, все публичные методы должны подтвердить свои аргументы и никогда не должны быть уверены, что все время при вызове будут указаны правильные значения (паттерн более известный Cómo sanity checks (санитарная проверка)). Возвращаясь к нашему примеру из предыдущего раздела, метод parse должен выполнить проверку своего единственного аргумента, прежде чем делать что-нибудь с ним: 1 public int parse( String str ) throws NumberFormatException { 2 if( str == null ) { 3 throw new IllegalArgumentException( "String should not be null" ); 4 } 5 6 return Integer.parseInt( str ); 7 } Java имеет другой вариант выполнения проверки и sanity checks, используя assert операторы. Однако, те, которые могли быть выключены во время выполнения и могут быть не выполнены. Предпочтительно, всегда выполнять такие проверки и вызывать соответствующие исключения. Даже имея documentoированные методы и проверку их аргументов, хочу сделать еще пару замечаний связанных с возвращаемыми значениями. До того Cómo вышла Java 8, самым простым способом сказать что метод в данное время не имеет значения чтобы его возвратить было просто вернуть нуль. Вот почему Java так плохо получить исключение NullPointerException. Java 8 пытается решить этот вопрос с введением Optional < T > class. Давайте взглянем на этот пример: 1 public< T > Optional< T > find( String id ) { 2 // Some implementation here 3 } Optional < T > предоставляет много полезных методов, и completamente устраняет необходимость возвращать в методе null и загрязнять везде ваш código проверками на null. Единственное исключение, вероятно, это коллекции. Всякий раз, когда метод возвращает коллекцию, всегда лучше вернуть null en lugar de null (и даже Optional < T >), например: 1 public< T > Collection< T > find( String id ) { 2 return Collections.emptyList(); 3 }
12. Метод Cómo точка входа в приложение
Incluso si usted es un simple desarrollador que escribe aplicaciones en su organización o un colaborador de uno de los marcos o bibliotecas de Java más populares, las decisiones de diseño que tome juegan un papel muy importante en cómo se utilizará su código. Si bien las pautas de diseño de API valen varios libros, esta parte del tutorial cubre muchos de ellos (cómo los métodos se convierten en el punto de entrada a una API), por lo que una descripción general rápida será muy útil: • Utilice nombres significativos para los métodos y sus argumentos (Método firmas) Intente mantener el número de argumentos en menos de 6 (sección Firmas de métodos) • Mantenga sus métodos breves y legibles (cuerpo del método y sección Inlining) • Siempre documente los métodos públicos, incluidas las condiciones previas y los ejemplos, si tiene sentido (sección Método Documentación) • Realice siempre comprobaciones de argumentos y controles de cordura (sección Parámetros de método y valores de retorno) • Intente evitar valores nulos como valores de retorno (sección Parámetros de método y valores de retorno) • Siempre que tenga sentido, intente diseñar métodos inmutables (que no no afecta el estado interno, sección Inmutabilidad) • Usar reglas de visibilidad y accesibilidad para ocultar métodos que no deberían ser públicos (parte 3 del tutorial, Cómo diseñar clases e interfaces)
13. ¿Qué sigue?
Esta parte del tutorial habla un poco menos sobre Java como lenguaje y más sobre cómo usar el lenguaje Java de manera efectiva, en particular cómo escribir métodos legibles, limpios, documentados y eficientes. En la siguiente sección, continuaremos con la misma idea básica y discutiremos los principios generales de programación diseñados para ayudarlo a convertirse en un mejor desarrollador de Java.
14. Descargar el código fuente
Esta lección trataba sobre cómo escribir métodos de forma eficaz. Puede descargar el código fuente aquí:
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION