Понятный код




Paul Malikov
содержание
• в чем проблема?
• названия
• функции
• комментарии
• форматирование
• классы
в чем проблема?
в чем проблема?
в чем проблема?
в чем проблема?
содержательные имена
имя должно передавать намерение программиста


  // elapsed time in days   int   elapsedTimeInDays;
  int d;                    int   daysSinceCreation;
  int dd;                   int   daysSinceModification;
  int d2;                   int   fileAgeInDays;
Явный код
       Неявный код
                                         лучше неявного
public List<int[]> getThem() {
    List<int[]> list1 =           public List<int[]> getFlaggedCells() {
        new ArrayList<int[]>();       List<int[]> flaggedCells =
                                          new ArrayList<int[]>();
    for (int[] x : theList)
                                      for (int[] cell : gameBoard)
        if (x[0] == 4)                    if (cell[STATUS_VALUE] == FLAGGED)
            list1.add(x);                     flaggedCells.add(cell);
                                      return flaggedCells;
    return list1;
                                  }
}


1. Что хранится в theList?        public List<Cell> getFlaggedCells() {
2. Почему так важен нулевой           List<Cell> flaggedCells =
                                          new ArrayList<Cell>();
элемент x[0]?                         for (Cell cell : gameBoard)
3. Что означает 4?                        if (cell.isFlagged())
                                              flaggedCells.add(cell);
4. Как использовать                   return flaggedCells;

возвращаемое значение?            }
Явный код
       Неявный код
                                         лучше неявного
public List<int[]> getThem() {
    List<int[]> list1 =           public List<int[]> getFlaggedCells() {
        new ArrayList<int[]>();       List<int[]> flaggedCells =
                                          new ArrayList<int[]>();
    for (int[] x : theList)
                                      for (int[] cell : gameBoard)
        if (x[0] == 4)                    if (cell[STATUS_VALUE] == FLAGGED)
            list1.add(x);                     flaggedCells.add(cell);
                                      return flaggedCells;
    return list1;
                                  }
}


1. Что хранится в theList?        public List<Cell> getFlaggedCells() {
2. Почему так важен нулевой           List<Cell> flaggedCells =
                                          new ArrayList<Cell>();
элемент x[0]?                         for (Cell cell : gameBoard)
3. Что означает 4?                        if (cell.isFlagged())
                                              flaggedCells.add(cell);
4. Как использовать                   return flaggedCells;

возвращаемое значение?            }
избегайте дезинформации

• избегайте названий, значение которых
  зависит от контекста

           hp - hipotenuse
      aix - augmentationIndex
      sco - spaceControlOficer


• hp, aix, sco - названия Unix платформ
избегайте дезинформации

• аккуратно используйте специфические
  программерские названия
   accountList - type of List<T>?

• используйте альтернативные названия
            accountGroup
           bunchOfAccounts
              accounts
избегайте дезинформации

• избегайте слабо отличающихся имен
  XYZControllerForHandlingOfStrings
  XYZControllerForStorageOfStrings
избегайте дезинформации

 • не используйте последовательную
    нумерацию
public static void copyChars(char a1[], char a2[]) {
    for (int i = 0; i < a1.length; i++) {
        a2[i] = a1[i];
    }
}

public static void copyChars(
    char source[],
    char destination[]) {
    for (int i = 0; i < source.length; i++) {
        destination[i] = source[i];
    }
}
избегайте дезинформации

• не используйте лишние слова (noise
  words)
        ProductInfo - Product
        theMessage - message
         moneyAmount - money
        accountData - account
используйте произносимые
          имена
• humans are good in words
    class DtaRcrd102 {
        private Date genymdhms;
        private Date modymdhms;
        private final String pszqint = "102";
    };

    class Customer {
        private Date generationTimestamp;
        private Date modificationTimestamp;
        private final String recordId = "102";
    };
используйте имена, удобные
        для поиска
• используйте имена переменных из одной
  буквы только в локальной области
  видимости в маленьких методах
e - самая встречаемая буква алфавита

• не используйте численные константы
  MAX_CLASSES_PER_STUDENT вместо 7
используйте имена, удобные
         для поиска
for (int j=0; j<34; j++) {
    s += (t[j]*4)/5;
}

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}
названия методов

• должны выражать действие
• с перегруженными конструкторами
  можно использовать static factory
  методы с названиями, описывающими
  агрументы
Complex fulcrumPoint = new Complex(23.0);

Complex fulcrumPoint = Complex.FromRealNumber(23.0);
размер методов

• методы должны быть маленкими
• еще меньше!
• это подразумевает однострочные if, esle,
  while (названия методов отлично
  документируют ветвление)
• и вложенные структуры не глубже
  второго уровня
public static String renderPageWithSetupsAndTeardowns(
    PageData pageData, boolean isSuite) throws Exception {
    boolean isTestPage = pageData.hasAttribute("Test");
    if (isTestPage) {
        WikiPage testPage = pageData.getWikiPage();
        StringBuffer newPageContent = new StringBuffer();
        includeSetupPages(testPage, newPageContent, isSuite);
        newPageContent.append(pageData.getContent());
        includeTeardownPages(testPage, newPageContent, isSuite);
        pageData.setContent(newPageContent.toString());
    }
    return pageData.getHtml();
}

public static String renderPageWithSetupsAndTeardowns(
    PageData pageData, boolean isSuite) throws Exception {
    if (isTestPage(pageData))
        includeSetupAndTeardownPages(pageData, isSuite);
    return pageData.getHtml();
}
правило одной операции

• функция должна выполнять только одну
  операцию
• она должна выполнять ее хорошо
• и ничего другого она делать не должна

• один уровень абстракции на функцию
private void includeSetupAndTeardownPages() {
    includeSetupPages();
    includePageContent();
    includeTeardownPages();
    updatePageContent();
}
private void includeSetupPages() {
    if (isSuite)
        includeSuiteSetupPage();
    includeSetupPage();
}
private void includeSuiteSetupPage() {
    include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
}
private void includeSetupPage() {
    include("SetUp", "-setup");
}
private void includePageContent() {
    newPageContent.append(pageData.getContent());
}
аргументы функции

• zero (niladic) - наилучший вариант
• one (monadic)
• two (dyadic)
• three (triadic)
• more (polyadic) - следует избегать
аргументы функции

• с точки зрения тестирования малое
  количество аргументов упрощает
  перебор комбинаций
• использование выходных аргументов
  нарушает общую идею подачи исходных
  данных в качестве аргументов и
  получение результата через
  возвращаемое значение функции
niladic




void saveTheMankind()
типичные случаи monadic

• проверка условия, связанного с
  аргументом
           boolean contains(“MyValue”)


• обработка аргумента, его
  преобразование и возвращение
          InputStream fileOpen(“MyFile”)



• сообщение о событии
  void passwordAttemptFailedNtimes(int attempts)
типичные случаи monadic

• старайтесь избегать выходных
  аргументов
        void transform(StringBuffer out)



• используйте возвращаемое значение
     StringBuffer transform(StringBuffer in)
monadic




Mrs marry(Miss girl)
типичные случаи diadic

• более сложны для понимания
      writeField(outputStream, name) ->
        outputStream.writeField(name)


• подходят для агрументов с естественным
  порядком
           Point p = new Point(x,y);
типичные случаи diadic

• если порядок не известен, можно
   преобразовать в monadic
compare(expected, actual) -> object.compare(expected)
diadic




compare(expected, actual)
разделение команд и запросов

 • функция должна либо делать что-то,
   либо отвечать на какой-то вопрос, но не
   оба действия вместе
 public boolean set(String attribute, String value);

 if (set("username", "unclebob"))...



 • метод устанавливает атрибут?
 • или метод проверяет значение атрибута?
разделение команд и запросов
 public boolean setAndCheckIfExists(String attribute,
 String value);



 if (attributeExists("username")) {
     setAttribute("username", "unclebob");
     ...
 }
использование исключений

• старайтесь использовать исключения
  вместо кодов ошибок в качестве
  возвращаемого значения
if (deletePage(page) == E_OK) {
    if (registry.deleteReference(page.name) == E_OK) {
         if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
             logger.log("page deleted");
         } else {
             logger.log("configKey not deleted");
         }
    } else {
         logger.log("deleteReference from registry failed");
    }
} else {
    logger.log("delete failed"); return E_ERROR;
}

try {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
    logger.log(e.getMessage());
}
комментарии

    • не делают плохой код лучше
    • объясняйте при помощи кода
// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) ...

if (employee.isEligibleForFullBenefits()) ...

boolean isEligibleForFullBenefits =
  (employee.flags & HOURLY_FLAG) && (employee.age > 65);
if (isEligibleForFullBenefits) ...
комментарии

• закомментированный код должен
  удаляться
• для читателя такой код означает, что есть
  серьезная причина, по которой этот код
  не был удален
форматирование
•   вертикальное форматирование и метафора
    газетной статьи
•   первый параграф дает вам общее описание
    статьи
•   прочитав его, вы определяете, та ли это статья,
    которая вам нужна
•   чем дальше, тем больше дателей
•   газета состоит из статей маленьких и побольше
•   если бы газета состояла из одной большой
    статьи, ее невозможно было бы читать
классы

• классы должны быть маленькие
• еще меньше!
• единица измерения - количество
  обязанностей (responsibility count)
название класса

• должно выражать его обязанность
• название, по сути, один из первых
  способов определить размер класса
• слова Processor, Manager, Super указывают
  на совмещение обязанностей
• опишите класс в 25 словах, не
  употребляя “если”, “и”, “или”, “но”
Single Responsibility Principle

• утверждает, что класс или модуль должен
  иметь одну и только одну причину для
  изменения
• наиболее часто игнорируется
  программистами, т.к. считают, что их
  работа закончена после того, как код
  начал работать
public class SuperDashboard {
    public Component getLastFocusedComponent();
    public void setLastFocused(Component lastFocused);
    public int getMajorVersionNumber();
    public int getMinorVersionNumber();
    public int getBuildNumber()
}

public class Version {
    public int getMajorVersionNumber();
    public int getMinorVersionNumber();
    public int getBuildNumber();
}
связность

• классы должны иметь небольшое
  количество переменных экземпляра
• каждый метод класса должен
  оперировать с этими переменными
• чем больше переменных, с которыми
  оперируют методы (для каждого
  метода), тем выше связность
public class Stack {
    private int topOfStack = 0;
    List<Integer> elements = new LinkedList<Integer>();

    public int size() {
        return topOfStack;
    }

    public void push(int element) {
        topOfStack++;
        elements.add(element);
    }

    public int pop() throws PoppedWhenEmpty {
        if (topOfStack == 0)
            throw new PoppedWhenEmpty();
        int element = elements.get(--topOfStack);
        elements.remove(topOfStack);
        return element;
    }
}
связность

• разбиение функций ведет к снижению
  связности
• если классы утрачивают связность,
  разбейте их!
что еще в этой книге?

• обработка ошибок
• модульные тесты
• системы
• формирование архитектуры
• многопоточность
• smells and heruistics
литература

• Robert C. Martin, Clean Code. A Handbook of
  Agile Software Craftsmanship - ISBN-13:
  978-0-13-235088-4


• Robert C. Martin, Agile Principles, Patterns,
  and Practices ISBN-13: 978-0135974445
• Martin Fowler, Refactoring: Improving the
  Design of Existing Code ISBN-13: 978-0201485677

[JAM 1.1] Clean Code (Paul Malikov)

  • 1.
  • 2.
    содержание • в чемпроблема? • названия • функции • комментарии • форматирование • классы
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
    содержательные имена имя должнопередавать намерение программиста // elapsed time in days int elapsedTimeInDays; int d; int daysSinceCreation; int dd; int daysSinceModification; int d2; int fileAgeInDays;
  • 16.
    Явный код Неявный код лучше неявного public List<int[]> getThem() { List<int[]> list1 = public List<int[]> getFlaggedCells() { new ArrayList<int[]>(); List<int[]> flaggedCells = new ArrayList<int[]>(); for (int[] x : theList) for (int[] cell : gameBoard) if (x[0] == 4) if (cell[STATUS_VALUE] == FLAGGED) list1.add(x); flaggedCells.add(cell); return flaggedCells; return list1; } } 1. Что хранится в theList? public List<Cell> getFlaggedCells() { 2. Почему так важен нулевой List<Cell> flaggedCells = new ArrayList<Cell>(); элемент x[0]? for (Cell cell : gameBoard) 3. Что означает 4? if (cell.isFlagged()) flaggedCells.add(cell); 4. Как использовать return flaggedCells; возвращаемое значение? }
  • 17.
    Явный код Неявный код лучше неявного public List<int[]> getThem() { List<int[]> list1 = public List<int[]> getFlaggedCells() { new ArrayList<int[]>(); List<int[]> flaggedCells = new ArrayList<int[]>(); for (int[] x : theList) for (int[] cell : gameBoard) if (x[0] == 4) if (cell[STATUS_VALUE] == FLAGGED) list1.add(x); flaggedCells.add(cell); return flaggedCells; return list1; } } 1. Что хранится в theList? public List<Cell> getFlaggedCells() { 2. Почему так важен нулевой List<Cell> flaggedCells = new ArrayList<Cell>(); элемент x[0]? for (Cell cell : gameBoard) 3. Что означает 4? if (cell.isFlagged()) flaggedCells.add(cell); 4. Как использовать return flaggedCells; возвращаемое значение? }
  • 18.
    избегайте дезинформации • избегайтеназваний, значение которых зависит от контекста hp - hipotenuse aix - augmentationIndex sco - spaceControlOficer • hp, aix, sco - названия Unix платформ
  • 19.
    избегайте дезинформации • аккуратноиспользуйте специфические программерские названия accountList - type of List<T>? • используйте альтернативные названия accountGroup bunchOfAccounts accounts
  • 20.
    избегайте дезинформации • избегайтеслабо отличающихся имен XYZControllerForHandlingOfStrings XYZControllerForStorageOfStrings
  • 21.
    избегайте дезинформации •не используйте последовательную нумерацию public static void copyChars(char a1[], char a2[]) { for (int i = 0; i < a1.length; i++) { a2[i] = a1[i]; } } public static void copyChars( char source[], char destination[]) { for (int i = 0; i < source.length; i++) { destination[i] = source[i]; } }
  • 22.
    избегайте дезинформации • неиспользуйте лишние слова (noise words) ProductInfo - Product theMessage - message moneyAmount - money accountData - account
  • 23.
    используйте произносимые имена • humans are good in words class DtaRcrd102 { private Date genymdhms; private Date modymdhms; private final String pszqint = "102"; }; class Customer { private Date generationTimestamp; private Date modificationTimestamp; private final String recordId = "102"; };
  • 24.
    используйте имена, удобные для поиска • используйте имена переменных из одной буквы только в локальной области видимости в маленьких методах e - самая встречаемая буква алфавита • не используйте численные константы MAX_CLASSES_PER_STUDENT вместо 7
  • 25.
    используйте имена, удобные для поиска for (int j=0; j<34; j++) { s += (t[j]*4)/5; } int realDaysPerIdealDay = 4; const int WORK_DAYS_PER_WEEK = 5; int sum = 0; for (int j=0; j < NUMBER_OF_TASKS; j++) { int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks; }
  • 26.
    названия методов • должнывыражать действие • с перегруженными конструкторами можно использовать static factory методы с названиями, описывающими агрументы Complex fulcrumPoint = new Complex(23.0); Complex fulcrumPoint = Complex.FromRealNumber(23.0);
  • 27.
    размер методов • методыдолжны быть маленкими • еще меньше! • это подразумевает однострочные if, esle, while (названия методов отлично документируют ветвление) • и вложенные структуры не глубже второго уровня
  • 28.
    public static StringrenderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception { boolean isTestPage = pageData.hasAttribute("Test"); if (isTestPage) { WikiPage testPage = pageData.getWikiPage(); StringBuffer newPageContent = new StringBuffer(); includeSetupPages(testPage, newPageContent, isSuite); newPageContent.append(pageData.getContent()); includeTeardownPages(testPage, newPageContent, isSuite); pageData.setContent(newPageContent.toString()); } return pageData.getHtml(); } public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception { if (isTestPage(pageData)) includeSetupAndTeardownPages(pageData, isSuite); return pageData.getHtml(); }
  • 29.
    правило одной операции •функция должна выполнять только одну операцию • она должна выполнять ее хорошо • и ничего другого она делать не должна • один уровень абстракции на функцию
  • 30.
    private void includeSetupAndTeardownPages(){ includeSetupPages(); includePageContent(); includeTeardownPages(); updatePageContent(); } private void includeSetupPages() { if (isSuite) includeSuiteSetupPage(); includeSetupPage(); } private void includeSuiteSetupPage() { include(SuiteResponder.SUITE_SETUP_NAME, "-setup"); } private void includeSetupPage() { include("SetUp", "-setup"); } private void includePageContent() { newPageContent.append(pageData.getContent()); }
  • 31.
    аргументы функции • zero(niladic) - наилучший вариант • one (monadic) • two (dyadic) • three (triadic) • more (polyadic) - следует избегать
  • 32.
    аргументы функции • сточки зрения тестирования малое количество аргументов упрощает перебор комбинаций • использование выходных аргументов нарушает общую идею подачи исходных данных в качестве аргументов и получение результата через возвращаемое значение функции
  • 33.
  • 34.
    типичные случаи monadic •проверка условия, связанного с аргументом boolean contains(“MyValue”) • обработка аргумента, его преобразование и возвращение InputStream fileOpen(“MyFile”) • сообщение о событии void passwordAttemptFailedNtimes(int attempts)
  • 35.
    типичные случаи monadic •старайтесь избегать выходных аргументов void transform(StringBuffer out) • используйте возвращаемое значение StringBuffer transform(StringBuffer in)
  • 36.
  • 37.
    типичные случаи diadic •более сложны для понимания writeField(outputStream, name) -> outputStream.writeField(name) • подходят для агрументов с естественным порядком Point p = new Point(x,y);
  • 38.
    типичные случаи diadic •если порядок не известен, можно преобразовать в monadic compare(expected, actual) -> object.compare(expected)
  • 39.
  • 40.
    разделение команд изапросов • функция должна либо делать что-то, либо отвечать на какой-то вопрос, но не оба действия вместе public boolean set(String attribute, String value); if (set("username", "unclebob"))... • метод устанавливает атрибут? • или метод проверяет значение атрибута?
  • 41.
    разделение команд изапросов public boolean setAndCheckIfExists(String attribute, String value); if (attributeExists("username")) { setAttribute("username", "unclebob"); ... }
  • 42.
    использование исключений • старайтесьиспользовать исключения вместо кодов ошибок в качестве возвращаемого значения
  • 43.
    if (deletePage(page) ==E_OK) { if (registry.deleteReference(page.name) == E_OK) { if (configKeys.deleteKey(page.name.makeKey()) == E_OK) { logger.log("page deleted"); } else { logger.log("configKey not deleted"); } } else { logger.log("deleteReference from registry failed"); } } else { logger.log("delete failed"); return E_ERROR; } try { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey()); } catch (Exception e) { logger.log(e.getMessage()); }
  • 44.
    комментарии • не делают плохой код лучше • объясняйте при помощи кода // Check to see if the employee is eligible for full benefits if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) ... if (employee.isEligibleForFullBenefits()) ... boolean isEligibleForFullBenefits = (employee.flags & HOURLY_FLAG) && (employee.age > 65); if (isEligibleForFullBenefits) ...
  • 45.
    комментарии • закомментированный коддолжен удаляться • для читателя такой код означает, что есть серьезная причина, по которой этот код не был удален
  • 46.
    форматирование • вертикальное форматирование и метафора газетной статьи • первый параграф дает вам общее описание статьи • прочитав его, вы определяете, та ли это статья, которая вам нужна • чем дальше, тем больше дателей • газета состоит из статей маленьких и побольше • если бы газета состояла из одной большой статьи, ее невозможно было бы читать
  • 47.
    классы • классы должныбыть маленькие • еще меньше! • единица измерения - количество обязанностей (responsibility count)
  • 48.
    название класса • должновыражать его обязанность • название, по сути, один из первых способов определить размер класса • слова Processor, Manager, Super указывают на совмещение обязанностей • опишите класс в 25 словах, не употребляя “если”, “и”, “или”, “но”
  • 49.
    Single Responsibility Principle •утверждает, что класс или модуль должен иметь одну и только одну причину для изменения • наиболее часто игнорируется программистами, т.к. считают, что их работа закончена после того, как код начал работать
  • 50.
    public class SuperDashboard{ public Component getLastFocusedComponent(); public void setLastFocused(Component lastFocused); public int getMajorVersionNumber(); public int getMinorVersionNumber(); public int getBuildNumber() } public class Version { public int getMajorVersionNumber(); public int getMinorVersionNumber(); public int getBuildNumber(); }
  • 51.
    связность • классы должныиметь небольшое количество переменных экземпляра • каждый метод класса должен оперировать с этими переменными • чем больше переменных, с которыми оперируют методы (для каждого метода), тем выше связность
  • 52.
    public class Stack{ private int topOfStack = 0; List<Integer> elements = new LinkedList<Integer>(); public int size() { return topOfStack; } public void push(int element) { topOfStack++; elements.add(element); } public int pop() throws PoppedWhenEmpty { if (topOfStack == 0) throw new PoppedWhenEmpty(); int element = elements.get(--topOfStack); elements.remove(topOfStack); return element; } }
  • 53.
    связность • разбиение функцийведет к снижению связности • если классы утрачивают связность, разбейте их!
  • 54.
    что еще вэтой книге? • обработка ошибок • модульные тесты • системы • формирование архитектуры • многопоточность • smells and heruistics
  • 55.
    литература • Robert C.Martin, Clean Code. A Handbook of Agile Software Craftsmanship - ISBN-13: 978-0-13-235088-4 • Robert C. Martin, Agile Principles, Patterns, and Practices ISBN-13: 978-0135974445 • Martin Fowler, Refactoring: Improving the Design of Existing Code ISBN-13: 978-0201485677