Как стать автором
Обновить

Рекомендации к стилю кода

Время на прочтение 9 мин
Количество просмотров 170K
Автор оригинала: Android Open Source Project

Правила языка Java


Мы следуем стандартным соглашениям по оформлению кода на Java. Мы добавили к ним некоторые правила:
  1. Исключения: никогда не перехватывайте и не игнорируйте их без объяснения.
  2. Исключения: не используйте обобщенные исключения, кроме кода в библиотеках, в корне стека.
  3. Финализаторы: не используйте их.
  4. Импорты: полностью уточняйте импорты.


Правила Java библиотек


Существуют соглашения, по поводу использования Java библиотек и инструментов для Android. В некоторых случаях соглашения могут быть изменены, например, в таких как использование старого кода, который, возможно, использует неодобренный паттерн или библиотеку.

Правила Java стиля


Программы гораздо проще поддерживать, когда все файлы имеют согласованный стиль. Мы следуем стандартному стилю программирования на Java, определенному Sun в их Code Conventions for the Java Programming Language, с несколькими исключениями и дополнениями. Данное руководство по стилю является подробным и всесторонним, а также широко используется Java сообществом.

В дополнение, мы обязываем использовать следующие правила для кода:
  1. Комментарии/Javadoc: пишите их; используйте стандартный стиль.
  2. Короткие методы: не пишите гигантских методов.
  3. Поля: должны быть вверху файла, или прямо перед методом, который их использует.
  4. Локальные переменные: ограничивайте область видимости.
  5. Импорты: android; сторонние (в алфавитном порядке); java(x)
  6. Отступы: 4 пробела, без табуляций.
  7. Длина строки: 100 символов.
  8. Имена полей: не public и не static поля начинаются с «m».
  9. Фигурные скобки: открывающие фигурные скобки не находятся в отдельной строке.
  10. Аннотации: используйте стандартные аннотации.
  11. Сокращения: используйте сокращения как слова в именах, например, XmlHttpRequest, getUrl() и т.п.
  12. Стиль TODO: «TODO: пишите описание здесь».
  13. Согласованность: смотрите, что находится вокруг вас.

Правила языка Java


Не игнорируйте исключения

Возможно, вам захочется написать код, который игнорирует исключения, например:
void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) { }
}

Никогда так не делайте. В то время как вы думаете, что ваш код никогда не столкнется с таким условием или, что неважно обрабатывать это условие, игнорирование исключений создает скрытые проблемы. Вы в принципе должны обрабатывать каждое исключение. Специфика в каждом конкретном случае зависит от ситуации.
Приемлимые альтернативы:
  • Перебрасывайте исключения к вызывающему методу.
    void setServerPort(String value) throws NumberFormatException {
        serverPort = Integer.parseInt(value);
    }
             

  • Выбрасывайте исключения, соответственно вашему уровню абстракции
    void setServerPort(String value) throws ConfigurationException {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new ConfigurationException("Port " + value + " is not valid.");
        }
    }
    

  • Перехватите ошибку и замените соответствующее значение в блоке catch{}
    /** Set port. If value is not a valid number, 80 is substituted. */
    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            serverPort = 80;  // default port for server 
        }
    }
    

  • Перехватите ошибку и выбросьте RuntimeException. Это опасно: делайте это только если вам все равно случится ли эта ошибка.
    /** Set port. If value is not a valid number, die. */
    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new RuntimeException("port " + value " is invalid, ", e);
        }
    }
    
    Заметьте, что изначальное исключение передается конструктору RuntimeException. Если вы используете компилятор Java 1.3, то опустите исключение.
  • Если вы уверены в том, что игнорирование исключения в этом случае имеет место, то хотя бы прокомментируйте, почему вы так решили.
    /** If value is not a valid number, original port number is used. */
    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            // Method is documented to just ignore invalid user input.
            // serverPort will just be unchanged.
        }
    }
    


Не перехватывайте обобщенные исключения

Иногда бывает заманчиво полениться с обработкой исключений и написать что-то вроде этого:
try {
    someComplicatedIOFunction();        // may throw IOException 
    someComplicatedParsingFunction();   // may throw ParsingException 
    someComplicatedSecurityFunction();  // may throw SecurityException 
    // phew, made it all the way 
} catch (Exception e) {               // I'll just catch all exceptions 
    handleError();                      // with one generic handler!
}

Вам не следует так делать. Суть в том, что возможно появление исключения, которого вы не ожидали и, в итоге, ошибка будет отлавливаться на уровне приложения. То есть, если кто-то добавит новый тип исключения, то компилятор не сможет вам помочь понять, что это другая ошибка.

Существуют редкие исключения из этого правила: определенный тестовый код, или код верхнего уровня, где вы хотите перехватывать все типы ошибок (для того, чтобы предотвратить их отображение в пользовательском интерфейсе, или чтобы продолжить какую-то пакетную задачу).

Альтернативы обобщенным исключениям:
  • Перехватывайте каждое исключение отдельно в блоке catch, после одиночного try. Возможно это неудобно, но всё равно это предпочтительный способ для перехвата всех исключений.
  • Измените ваш код для более гранулированной обработки ошибок с несколькими блоками try. Отделите IO от парсинга, обрабатывайте ошибки отдельно в каждом случае.
  • Перебросьте исключение. Во многих случаях вам не нужно обрабатывать все исключения на текущем уровне, просто позвольте методу перебросить их.

Помните: исключения — ваши друзья! Не сердитесь, когда компилятор указывает на то, что вы их не отлавливаете.

Финализаторы

Что это: Финализаторы — это способ запускать программный код перед тем как объект собирается сборщиком мусора.
За: могут быть полезны при очистке, в особенности внешних ресурсов.
Против: нет никаких гарантий того, когда будет вызван финализатор, и, вообще, будет ли он вызван.

Решение: Мы не используем финализаторы. В большинстве случаев, всё то, что вам нужно от финализатора, вы сможете сделать при помощи обработки исключений. Если вам действительно нужен финализатор, то объявите метод close() и задокументируйте, когда он точно будет вызываться.

Импорты

Групповой символ в импортах

Что это: Когда вы хотите использовать класс Bar из пакета foo, то есть два способа сделать это:
  1. import foo.*;
  2. import foo.Bar;

За #1: Потенциально уменьшает количество возможных операторов импорта.
За #2: Делает явным то, какой класс на самом деле используется. Делает код более удобочитаемым для тех, кто его поддерживает.

Решение: Используйте стиль #2 для импорта любого Android кода. Явное исключение делается для стандартных библиотек (java.util.*, java.io.*, и т.п) и для кода модульного тестирования (junit.framework.*).

Комментарии/Javadoc


Каждый файл должен иметь объявление об авторских правах в самом начале. Далее идут объявления операторов package и import, причем каждый блок разделяется пустой строкой. За ними следуют объявления класса или интерфейса. Опишите, что делает класс в Javadoc-комментариях.
/*
 * Copyright (C) 2010 The Android Open Source Project 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package com.android.internal.foo;

import android.os.Blah;
import android.view.Yada;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Does X and Y and provides an abstraction for Z.
 */
public class Foo {
    ...
}

Каждый класс и нетривиальный public метод должен содержать Javadoc, по крайней мере с одной фразой, описывающей что он делает. Фраза должна начинаться с описательного глагола 3-го лица. Примеры:
/** Returns the correctly rounded positive square root of a double value. */
static double sqrt(double a) {
}

/**
 * Constructs a new String by converting the specified array of 
 * bytes using the platform's default character encoding.
 */
public String(byte[] bytes) {
}

Вам не нужно описывать Javadoc для тривиальных get и set методов, таких как setFoo(), если ваш Javadoc говорит только «sets Foo». Если метод делает что-то более сложное (например, соблюдение неких ограничений, или если его действия имеют важный эффект вне его самого), тогда его обязательно нужно задокументировать. И если это не просто объяснение того, что означает Foo, то вам также следует его задокументировать.

Вообще, любой метод, который вы написали получает пользу от Javadoc, неважно public он или нет. Public методы являются частью API, и поэтому они требуют описания в Javadoc.

Для написания Javadoc'ов вам следует придерживаться Sun Javadoc conventions.

Короткие методы


Методы должны быть небольшими и решающими конкретную задачу настолько, насколько это возможно. Однако, понятно, что иногда большие методы бывают целесообразны, так что нет строгого ограничения на длину метода. Если метод превышает 40 строк, то вам, возможно, стоит подумать о том, можно ли его разбить на части, не нарушив структуры программы.

Локальные переменные


Область видимости локальных переменных должна сводиться к минимуму. Делая это, вы улучшаете читаемость и поддерживаемость кода, а также уменьшаете вероятность ошибок. Каждая переменная должна объявляться в самом глубоком блоке, который окружает все возможные места использования переменной.

Локальные переменные должны объявляться в том месте, где впервые необходимо её использовать. Почти каждая локальная переменная нуждается в инициализаторе. Если вы еще не знаете, как точно инициализировать переменную, то вам следует отложить её объявление, пока вы это не узнаете.

Существует одно исключение, касательно блока try-catch. Если переменная инициализируется при помощи оператора return метода, который выбрасывает проверяемое исключение, то она должна инициализироваться в блоке try. Если же переменная должна использоваться вне блока try, тогда она объявляется перед ним, неважно, знаете ли вы как её точно нужно инициализировать:
// Instantiate class cl, which represents some sort of Set 
Set s = null;
try {
    s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
    throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
    throw new IllegalArgumentException(cl + " not instantiable");
}

// Exercise the set 
s.addAll(Arrays.asList(args));

Но даже этот случай можно обойти при помощи инкапсуляции блока try-catch в методе.
Set createSet(Class cl) {
    // Instantiate class cl, which represents some sort of Set 
    try {
        return (Set) cl.newInstance();
    } catch(IllegalAccessException e) {
        throw new IllegalArgumentException(cl + " not accessible");
    } catch(InstantiationException e) {
        throw new IllegalArgumentException(cl + " not instantiable");
    }
}

...

// Exercise the set 
Set s = createSet(cl);
s.addAll(Arrays.asList(args));

Переменные в циклах должны объявляться внутри самого оператора, если только нет непреодолимой причины этого не делать.
for (int i = 0; i <= n; i++) {
    doSomething(i);
}

for (Iterator i = c.iterator(); i.hasNext(); ) {
    doSomethingElse(i.next());
}


Импорты


Порядок операторов импорта следующий:
  1. Android импорты.
  2. Сторонние импорты (com, junit, net, org).
  3. java и javax.

Для полного соответствия настройкам IDE, импорты должны иметь следующий вид:
  • Отсортированы по алфавиту внутри каждой группы.
  • Заглавные буквы должны быть впереди букв нижнего регистра (например, Z перед a).
  • Главные группы должны разделяться пустой строкой.

Почему?
Порядок такой, чтобы:
  • Импорты, которые люди хотят видеть в первую очередь, находятся вверху (android).
  • Импорты, которые люди хотят видеть в последнюю очередь, находятся внизу (java).
  • Люди могут с легкостью использовать этот стиль.
  • IDE может придерживаться этого стиля.

Отступы


мы используем 4 пробела для блоков. Мы никогда не используем табуляцию. Мы используем 8 пробелов для переноса строк, включая вызовы функций и присваивания, например правильно так:
Instrument i =
        someLongExpression(that, wouldNotFit, on, one, line);

а так неверно:
Instrument i =
    someLongExpression(that, wouldNotFit, on, one, line);

Названия полей


  • Не static и не public имена начинаются c «m».
  • static поля начинаются с «s».
  • Другие поля начинаются с буквы нижнего регистра.
  • Поля public static final (константы) пишутся полностью в верхнем регистре, с использованием подчеркивания (ALL_CAPS_WITH_UNDERSCORES)

Например:
public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

Фигурные скобки


Для открывающих фигурные скобок не выделяется отдельная строка, они находятся в той же строке, что и код перед ними:
class MyClass {
    int func() {
        if (something) {
            // ...
        } else if (somethingElse) {
            // ...
        } else {
            // ...
        }
    }
}

Мы требуем фигурные скобки для оператора условия. Исключением является, когда оператор условия и его тело помещаются в одну строку. То есть можно писать так:
if (condition) {
    body(); // ok 
}
if (condition) body(); // ok

Но так нельзя:
if (condition)
    body(); // bad

Длина строки


Каждая строка текста в коде должна быть не длиннее 100 символов.
Исключение: если комментарий содержит пример команд, или URL (удобнее использовать copy/paste).
Исключение: строки импорта могут быть длиннее 100 символов, так как люди редко на них смотрят. Также это упрощает написание инструментов.

Сокращения в именах


Рассматривайте сокращения и аббревиатуры как слова. Имена более удобочитаемы:
Хорошо Плохо
XmlHttpRequest XMLHTTPRequest
getCustomerId getCustomerID

Этот стиль также применяется, когда сокращение и аббревиатура — это полное имя:
Хорошо Плохо
class Html class HTML
String url; String URL;
long id; long ID;


Стиль TODO


Используйте комментарии TODO для кода, который является временным, краткосрочным, или хорошим, но не идеальным. Комментарий должен включать в себя «TODO:», например:
// TODO: Remove this code after the UrlTable2 has been checked in.

// TODO: Change this to use a flag instead of a constant.

Если ваш комментарий имеет вид «В будущем сделать что-то», то убедитесь, что он включает в себя конкретную дату (1 января 2011 года), или конкретное событие «Удалить после выхода версии 2.1».

Согласованность


Если вы изменяете код, то потратьте минуту на то, чтобы посмотреть на код вокруг вас и определить его стиль. Если в нем используются пробелы, то и вам следует их использовать. Если комментарии содержат небольшой набор звездочек, то и вам следует их использовать.

Весь смысл рекомендаций к стилю кода в создании общей лексики, чтобы люди концентрировались на том, что они говорят, вместо того как они говорят. Мы представляем глобальные правила стиля, чтобы люди знали эту лексику. Но локальный стиль также важен. Если код, который вы добавляете в файл выглядит резко отличным от того, что был, то это выбросит будущего читателя из его ритма и будет мешать ему понимать структуру. Старайтесь избегать этого.
Теги:
Хабы:
+28
Комментарии 36
Комментарии Комментарии 36

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн