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

Original author: Android Open Source Project
  • Translation

Правила языка 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».

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


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

Весь смысл рекомендаций к стилю кода в создании общей лексики, чтобы люди концентрировались на том, что они говорят, вместо того как они говорят. Мы представляем глобальные правила стиля, чтобы люди знали эту лексику. Но локальный стиль также важен. Если код, который вы добавляете в файл выглядит резко отличным от того, что был, то это выбросит будущего читателя из его ритма и будет мешать ему понимать структуру. Старайтесь избегать этого.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 36

    +1
    В первом классе вырабативаем калиграфический почерк, а повзрослев выабатываем стиль програминга.
    А вобще очень полезная статья, лучше привыкать сразу писать читабильный и корректный код.
    Сейчас работодатели очень на это обращают внимание, да и для себя приятней разбираться в хорошо оформленном коде, а не разбирать бардак, что налепил вчера.
      +2
      Вроде бы все понятно, воде бы ничего нового, но тем не менее полезно иногда перечитывать такие посты.

      Только вот ограничение в 100 символов на длину строки кажется неудобным. В нашей компании стандарт — 120 символов. Если, например, объявляешь коллекцию объектов с длиным названием в переменной с длинным названием и в той же строке эту переменную инициализируешь, вызывая метод с длинным названием у объекта с пусть даже не самым длинным названием, тут-то ста символов и не хватает. А встречается такое часто.
        +7
        Не очень много отличий от java code conventions, а из андроид-специфичных — так и вовсе только порядок импортов.
        Причем те отличия, которые действительно будут видны — несколько спорны. Зачем, например, добавлять m к приватным полям класса? Чтобы автокомплит запутать?
          –1
          >Зачем, например, добавлять m к приватным полям класса

          Встречается ситуация, когда setter-метод использует параметры, совпадающие по имени с полями класса, видимо поэтому решили приписать префикс.
            +10
            Для этих редких ситуаций вполне можно использовать this.
            void setName(String name)
            {
            this.name = name;
            }

            У меня IDE визуально разграничивает их — по-разному отображает локальные переменные и переменные класса. Тут не спутаешь.
            ИМХО, префикс m — лишнее.
              0
              С принципом бритвы Оккама, конечно, не поспоришь. Но я всё-таки склоняюсь к тому, что добавление «m» уменьшает потенциальное количество багов, потому что есть вероятность забыть про this. Короче говоря, лучше использовать правило 13, то есть согласовывать свой код с тем, что уже есть. Ну или с тем, что принят в качестве корпоративного стандарта.
                +1
                Правило 13 — это веский аргумент, но много ли под Андроид «длинных» проектов с уже устоявшимся стилем? Думаю, не очень — именно поэтому и хотят ввести какие-то соглашения. Пока не поздно)
                Про this — не понял. Имя параметра сетера, равное имени локального филда — это скорее правило, чем исключение. Забыть про this я не знаю как можно, в 95% случаев геттеры/сеттеры генерятся IDE. Да и что собственно изменится? Ну назвали филд не field, а mField. Все равно же будет сеттер setMField с таким же параметром mField. Или они предлагают сеттер называть setField, нарушив спецификацию java beans?
                Врядли это цель — скорее уж я поверю в повышение наглядности за счет того, что явно видны различия между статик и обычными филдами, как когда-то добавляли к имени идентификаторы типа, вроде bdField для BigDecimal. Но актуально ли это сейчас, с учетом возможностей IDE? Не уверен… И, как специально, к этому пункту они не написали ни «pros», ни «cons», ни «why».
                  0
                  Первоисточник был написан для самого Android OSP, возможно, дело в этом?
                    0
                    >Или они предлагают сеттер называть setField, нарушив спецификацию java beans?
                    Опичание JavaBean как раз вроде бы говорит, что название сеттеров/геттеров может быть абстрагировано от названия приватных полей и даже более того: таких приватных полей может и не быть.
                  • UFO just landed and posted this here
                    +2
                    Префикс m удобно использовать. Но для всех переменных экземпляра, а не только приватных. Например, так можно отделить визуально переменные экземпляра от локальных переменных методов или параметров методов!
                    Хотя, все это дело каждой организации/команды разработчиков и т.д.
                    Но признаться честно, раньше никогда сам не использовал данный префикс и все же сейчас понимаю, что удобная штука, ИМХО.
                      –1
                      Ещё с префиксами удобнее подсказки использовать. Например, в Eclipse написал 'm', кликнул Ctrl+Space и сразу видишь все переменные класса.
                  +2
                  Стандарты это хорошо, но использовать из надо с умом. Ничто не является правдой в последней инстанции.
                    0
                    Правила имеют рекомендательный характер. Строгие они для тех, кто участвует в Android Open Source Project.
                    –4
                    Отступы в виде четырёх пробелов??? Кому и зачем это пришло в голову?
                    • UFO just landed and posted this here
                        +1
                        Так табуляция для того и придумана, чтобы не было этих проблем. Если юзать табы то каждый будет видеть такие отступы, какие ему по вкусу. Не понимаю как можно было четыре пробела сделать стандартом =\
                          0
                          Потому что стандарт табуляции — 8 символов в юниксе, а 4 — это не табуляция. Тоже предмет холивара.
                            0
                            Таб — это один символ, и каждая IDE может отображать его как хочет — хоть в виде тридцати пробелов. Когда все ставят в коде табы, а не пробелы, проблем никаких не возникает и все довольны — у всех отображается то количество пробелов, какое они настроили в IDE. Но когда кто-то умный начинает ставить реальные проблемы — настаёт писец при смешивании кода, приходится с матом всё переформатировать.
                              0
                              А где гарантия, что в IDE всегда будут табы вместо пробелов?
                                0
                                Ну, я не уверен, но мне кажется что во всех нормальных IDE можно указать, чем индентить — каким-то числом пробелов или табами.
                          +2
                          Ну и чем принципиально отличается 4 пробела от табуляции, при использован ии IDE? Все так же нажимаешь Tab, на клавиатуре.
                          • UFO just landed and posted this here
                              0
                              Так об этом и речь, вот стандарт, в нем все прописано.
                              А нет, начинают снова использовать табуляцию и ставить скобки с новой строки.
                              • UFO just landed and posted this here
                                • UFO just landed and posted this here
                              0
                              Тем, что если ты написанный таким способом код отдашь другому человеку, у которого IDE настроен на просто табы или на восьмирные а не четверные пробелы, то он тебя проклянёт :) Представь, как будет выглядеть кусок твоего кода, скопипастенный в чужой проект.
                              Юзать надо ТОЛЬКО табы.
                                0
                                1. есть стандарт
                                2. код все равно будет выглядеть криво при копировании, так что в любом случае нужно будет нажать Ctrl+F?..

                                Так и в чем тут проблема?
                                  0
                                  1. При разработке стандартов надо думать головой
                                  2. Код никогда не выглядит криво при копировании, если и там и там юзались табы.
                                  3. Зачем вообще нужны стандарты если есть Ctrl+F? :))
                                    0
                                    Ну так форматер, форматирует код согласно тому что вы указали.

                                    В данном случае, рассматривается андроид, и логично было бы форматировать согласно принятым правилам, дабы ваш код не выглядел потом чужеродным.
                        • UFO just landed and posted this here
                            0
                            Документ в целом хороший, хоть и тоже не очень понял, причем тут андроид. Стандартный кодегайд по Яве. Но стоит отметить отдельные странные места.

                            Иногда возникает ощущение, что я живу в каком-то другом мире програмирования, а другие програмируют на notepad, где среда не подсвечивает поля класса, где люди руками смотрят в импорты чаще раза в месяц. Где среда на коммите не автоформатирует класс согласно общекомандному стилю и кого-то действительно волнует, что там в секции импортов сидит. У меня эта секция свернута средой и поддерживается средой. А уж про венгерскую нотацию сколько было статей, что подбирать префиксы по типу переменной (мембер, статик, локальная итп) или по классу это зло.
                            local.joelonsoftware.com/mediawiki/index.php/%D0%9A%D0%B0%D0%BA_%D0%B7%D0%B0%D1%81%D1%82%D0%B0%D0%B2%D0%B8%D1%82%D1%8C_%D0%BD%D0%B5%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4_%D0%B2%D1%8B%D0%B3%D0%BB%D1%8F%D0%B4%D0%B5%D1%82%D1%8C_%D0%BD%D0%B5%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE

                            Еще бросилась в глаза подробнейшая инструкция по эксепшенам, которая приводит к одной из самых распространенной болезни в кодировании, чрезмерному увлечению троваблами, когда кидание эксепшена является рядовым возвратом из функции. И это на андроиде, где перфоманс ключевой фактор. То, что эксепшены надо корректно отрабатывать, мысль вполне понятная, и все ветки исполнения должны адекватно читаться, это тоже понятно.
                              0
                              Финализаторы, кстати, очень удобно использовать для логгинга для детекта ликов. Если финализатор обнаружит, что на объекте не вызван close() он может выводить в лог с-щее предупреждение. Очень удобно.
                              0
                              >>Перебрасывайте исключения к вызывающему методу.
                              >>
                              >>void setServerPort(String value) throws NumberFormatException {
                              >> serverPort = Integer.parseInt(value);
                              >>}

                              Наверное рантаймы писать в throws — перебор:)
                                0
                                Steve McConnell «Code complete»
                                  0
                                  Опять египетские скобки, опять m в переменных. Не нужно.

                                  Only users with full accounts can post comments. Log in, please.