Сводка по чтению чистого кода

Java JavaScript спецификация кода

1 запуск

Эту статью читаютclean codeНекоторые резюме в то время, оригинальная книга основана на Java, вот некоторые из принципов, которые некоторые люди считают более практичными и простыми в сочетании с повседневным развитием бизнеса, и ссылаются на них.clean-code-javascriptВ этой статье приведены некоторые примеры кода, я надеюсь, что эта статья может дать некоторые ссылки для ежедневного кодирования разработки и рефакторинга.

2 Значимое название

2.1 Соответствует своему названию

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

// bad
var d = 10;
var oVal = 20;
var nVal = 100;


// good
var days = 10;
var oldValue = 20;
var newValue = 100;

2.2 не вводить в заблуждение

Именование не должно заставлять людей неправильно понимать информацию (тип, роль) переменной.

Учетные записи и учетной записи, если accountList действительно не является типом списка, в противном случае учетные записи будут лучше, чем аккуратный список. Поэтому, как список, эта карта суффикса, не свободная в использовании.

// bad
var platformList = {
    web: {},
    wap: {},
    app: {},
};


// good
var platforms = {
    web: {},
    wap: {},
    app: {},
};

2.3 Делайте значимые различия

Используйте явные значения, чтобы выразить прямые различия между переменными.

Во многих случаях будут такие имена, как product, productData и productInfo. Во многих случаях нет очевидной разницы между Data и Info. Лучше использовать product напрямую.

// bad
var goodsInfo = {
    skuDataList: [],
};

function getGoods(){};          // 获取商品列表
function getGoodsDetail(id){};  // 通过商品ID获取单个商品


// good
var goods = {
    skus: [],
};

function getGoodsList(){};      // 获取商品列表
function getGoodsById(id){};    // 通过商品ID获取单个商品

2.4 Используйте произносимые имена

Аббревиатура должна иметь степень, например писать типа DAT, будь то DATA или DATE...

// bad
var yyyyMMddStr = eu.format(new Date(), 'yyyy-MM-dd');
var dat = null;
var dev = 'Android';


// good
var todaysDate = eu.format(new Date(), 'yyyy-MM-dd');
var data = null;
var device = 'Android';

2.5 использовать имя с возможностью поиска

Имя с возможностью поиска может помочь быстро найти код, особенно для некоторых цифровых кодов состояния. Не рекомендуется использовать значение напрямую, а использовать перечисление.

// bad
var param = {
    periodType: 0,
};


// good
const HOUR = 0, DAY = 1;
var param = {
    periodType: HOUR,
};

2.6 Избегайте префиксов элементов

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

2.7 Добавить значимый контекст

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

firstName, lastName, street, houseNumber, city, state, zipcode объединяют ряд переменных на основе адреса, но если эти переменные работают по отдельности, значение имени некоторой переменной неясно. Затем вы можете добавить контекст, чтобы прояснить его значение, например, addrFirstName, addrLastName, addrState.

// bad
var firsName, lastName, city, zipcode, state;
var sku = {
    skuName: 'sku0',
    skuStorage: 'storage0',
    skuCost: '10',
};


// good
var addrFirsName, addrLastName, city, zipcode, addrState;
var sku = {
    name: 'sku0',
    storage: 'storage0',
    cost: '10',
};

2.8 Имена переменных всегда одинаковы

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

// bad
function searchGoods(searchText) {
    getList({
        keyword: searchText,
    });
}
function getList(option) {

}

// good
function searchGoods(keyword) {
    getList({
        keyword: keyword,
    });
}

function getList(keyword) {}

3 функции

3.1 короткий

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

// bad
function initPage(initParams) {
    var data = this.data;
    if ('dimension' in initParams) {
        data.dimension = initParams.dimension;
        data.tab.source.some(function(item, index){
            if (item.value === data.dimension) {
                data.tab.defaultIndex = index;
            }
        });
    }
    if ('standardMedium' in initParams) {
        data.hasStandardMedium = true;
        data.filterParams[data.dimension].standardMedium = initParams.standardMedium;
    }
    if ('plan' in initParams || 'name' in initParams) {
        data.filterParams[data.dimension].planQueryString = initParams.plan || initParams.name;
    } else if ('traceId' in initParams) {
        data.filterParams[data.dimension].planQueryString = 'id:' + initParams.traceId;
    }
}

// good
function initPage(initParams) {
    initDimension(initParams);
    initStandardMedium(initParams);
    initPlanQueryString(initParams);
}
function initDimension(initParams) {
    var data = this.data;
    if ('dimension' in initParams) {
        data.dimension = initParams.dimension;
        data.tab.source.some(function(item, index){
            if (item.value === data.dimension) {
                data.tab.defaultIndex = index;
            }
        });
    }
}
function initStandardMedium(initParams) {
    var data = this.data;
    if ('standardMedium' in initParams) {
        data.hasStandardMedium = true;
        data.filterParams[data.dimension].standardMedium = initParams.standardMedium;
    }
}
function initPlanQueryString() {
    var data = this.data;
    if ('plan' in initParams || 'name' in initParams) {
        data.filterParams[data.dimension].planQueryString = initParams.plan || initParams.name;
    } else if ('traceId' in initParams) {
        data.filterParams[data.dimension].planQueryString = 'id:' + initParams.traceId;
    }
}

3.2 Делайте только одно

Функция должна делать одну вещь, делать это хорошо и делать только одну вещь.

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

Когда функцию можно разделить на разделы (блоки кода), это означает, что функция делает слишком много.

// bad
function onTimepickerChange(type, e) {
    if(type === 'base') {
        // do base type logic...
    } else if (type === 'compare') {
        // do compare type logic...
    }
    // do other stuff...
}

// good
function onBaseTimepickerChange(e) {
    // do base type logic
    this.doOtherStuff();
}

function onCompareTimepickerChange(e) {
    // do compare type logic
    this.doOtherStuff();
}

function doOtherStuff(){}

3.3 Один уровень абстракции на функцию

Функцию не следует смешивать на более абстрактном уровне, т.е. один и тот же уровень делает шаги только в функцию, потому что одна вещь сможет полностью выполнить эти шаги.

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

Уровень абстракции можно разделить по нисходящему принципу

程序就像是一系列 TO 起头的段落,每一段都描述当前层级,并引用位于下一抽象层级的后续 TO 起头段落
- 如果要完成 A,需要完成 B,完成 C;
- 要完成 B,需要完成 D;
- 要完成 C,需要完成 E;

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

Фактический бизнес-пример, обзор канбан-трафика портала данных — это пример получения графика тренда и списка справа. Выберите вкладку, чтобы выбрать разные индикаторы, графики тенденций, на которые влияют разные индикаторы, и содержимое списка справа, данные двух модулей объединены в один запрос. Метод записи бегущей учетной записи позволяет написать функцию следующим образом Этот метод записи имеет несколько очевидных недостатков:

  • длинная. В нормальных условиях конфигурация диаграммы тренда может потребовать более 20 строк, а вся функция может запросто превысить 50 строк;
  • Название функции не точное. Имя функции показано только для получения диаграммы, но фактически для получения правильного списка данных и конфигурации;
  • Функциональный уровень сбивает с толку, и его можно разделить на более подробные;

По нисходящему принципу

// bad
getChart: function(){
    var data = this.data;
    var option = {
        url: '/chartUrl',
        param: {
            dimension: data.dimension,
            period: data.period,
            comparePeriod: data.comparePeriod,
            periodType: data.periodType,
        },
        fn: function(json){
            var data = this.data;
            // 设置图表
            data.chart = json.data.chart;
            data.chart.config = {
                //... 大量的图表配置,可能有20多行
            }
            // 设置右边列表
            data.sideList = json.data.list;
        }
    };
    // 获取请求参数
    this.fetchData(option);
},

// good
getChartAndSideList: function(){
    var option = {
        url: '/chartUrl',
        param: this.getChartAndSideListParam();
        fn: function(json){
            this.setChart(json);
            this.setSideList(json);
        }
    };
    this.fetchData(option);
},

3.4 оператор переключения

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

Смысл размещения его внизу можно понимать как захоронение его под абстрактной фабрикой и использование абстрактной фабрики для возврата методов или объектов с различными коннотациями для обработки.

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

Примеры данных внутри портала:

// bad
function getSum(a [, b, c, d, e ...]){}


// good
function getSum(arr){}
// bad
function exportExcel(url, param, onsuccess, onerror){}


// good
/**
 * @param option
 *    @property url
 *    @property param
 *    @property onsucces
 *    @property onerror
 */
function exportExcel(option){}

Параметр как можно меньше, лучше не превышать 3

函数应该取个好一点的名字,适当使用动词和关键字可以提高函数的可读性。 Например:

Функция, которая определяет, находится ли он в определенном диапазоне, называетсяwithin, о назначении функции можно легко судить по названию, но это все же не самое лучшее, так как эта функция имеет три параметра, с первого взгляда увидеть взаимосвязь между тремя параметрами этой функции невозможно, онаb <= a && a<= c,ещеa <= b && b <= c ?

Возможно, вы можете выразить связь между тремя параметрами, изменив имя параметра, это должно увидеть использование функции после определения функции.

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

// bad
function within(a, b, c){}

// good
function assertWithin(val, min, max){}

// good
function assertValWithinMinAndMax(val, min, max){}

3.7 Отсутствие побочных эффектов

Функция с побочными эффектами обычно является нечистой функцией, что означает, что функция выполняет более одной функции.

4 примечания

4.1 Хорошие заметки

Юридическая информация, информативные комментарии, объяснение намерений, уточнение, предостережение, TODO, преувеличение (увеличение важности некоторого, казалось бы, необоснованного кода), общедоступные комментарии к API

Попробуйте сделать функции, переменные становятся меткой, не полагайтесь на комментарии для описания, для некоторых тонкостей только с соответствующими пояснительными примечаниями.

4.2 Плохие комментарии

Бормотание про себя, избыточные комментарии (например, исходное название функции может объяснить замысел, но и добавить комментарии), вводящие в заблуждение комментарии, комментарии в стиле follow-by-style (для того, чтобы добавить комментарии к спецификации, по сути, имя функции а имя параметра уже может уточнять информацию), комментарии к логам (заметки, которые записывают бесполезные логи модификаций), бредовые комментарии

4.3 Принципы

// bad
var d = 10;     // 天数

// good
var days = 10;

Портал данных - Обзор в режиме реального времени внутренней части куска кода,/src/javascript/realTimeOverview/components/index.js

// bad
function dimensionChanged(dimension){
    var data = this.data.keyDealComposition;
    data.selectedDimension = dimension;
    // 2016.10.31 modify:产品改动,选择品牌分布的时候不显示二级类目
    // if (dimension.dimensionId == '6') {
    //     data.columns[0][0].name = dimension.dimensionName;
    //     data.columns[0].splice(1, 0, {name:'二级类目', value:'secCategoryName', noSort: true});
    // } else {
        this.handle('util.setTableHeader');
    // }
    this.handle('refreshComposition');
};

// good
function dimensionChanged(dimension){
    var data = this.data.keyDealComposition;
    data.selectedDimension = dimension;
    this.handle('util.setTableHeader');
    this.handle('refreshComposition');
};
  1. Не добавляйте слишком много информации в заметки внутри, никто не увидит

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

// bad
/**
 * 设置表格表头
 */
function setTableHeader(){},

// good
function setTableHeader(){},
  1. комментарии после скобок
// bad
function doSomthing(){
    while(!buffer.isEmpty()) {  // while 1
        // ...
        while(arr.length > 0) {  // while 2
            // ...
            if() {

            }
        } // while 2
    } // while 1
}
  1. Не нужно регистрировать тип, тип владения комментариями, я верю систему контроля версий
// bad
/**
 * 2016.12.03 bugfix, by xxxx
 * 2016.11.01 new feature, by xxxx
 * 2016.09.12 new feature, by xxxx
 * ...
 */


// bad
/**
 * created by xxxx
 * modified by xxxx
 */
function addSum() {}

/**
 * created by xxxx
 */
function getAverage() {
    // modified by xxx
}
  1. Старайтесь не использовать маркеры местоположения
// bad

/*************** Filters ****************/

///////////// Initiation /////////////////

5 форматов

5.1 Вертикальное направление

  1. Соответствующий код отображается компактно, а разные части разделены пробелами.
// bad
function init(){
    this.data.chartView = this.$refs.chartView;
    this.$parent.$on('inject', function () {
        this.dataConvert(this.data.source);
        this.draw();
    });
    this.$watch('source', function (newValue, oldValue) {
        if (newValue && newValue != this.data.initValue) {
            this.dataConvert(newValue);
            this.draw();
        } else if (!newValue) {
            if (self.data.chartView) {
                this.data.chartView.innerHTML = '';
            }
        }
    }, true);
}

// good
function init(){
    this.data.chartView = this.$refs.chartView;

    this.$parent.$on('inject', function () {
        this.dataConvert(this.data.source);
        this.draw();
    });

    this.$watch('source', function (newValue, oldValue) {
        if (newValue && newValue != this.data.initValue) {
            this.dataConvert(newValue);
            this.draw();
        } else if (!newValue) {
            if (this.data.chartView) {
                this.data.chartView.innerHTML = '';
            }
        }
    }, true);
}
  1. Не добавляйте в код слишком много длинных комментариев, мешающих чтению кода
// bad
BaseComponent.extend({
    checkAll: function(status){
        status = !!status;
        var data = this.data;
        this.checkAllList(status);
        this.checkSigList(status);
        data.checked.list = [];
        if(status){
            // 当全选的时候先清空列表, 然后在利用Array.push添加选中项
            // 如果在全选的时候不能直接checked.list = dataList
            // 因为这样的话后面对checked.list的操作就相当于对dataList直接进行操作
            // 利用push可以解决这一个问题
            data.sigList.forEach(function(item,i){
                data.checked.list.push(item.data.item);
            })
        }
        this.$emit('check', {
            sender: this,
            index: CHECK_ALL,
            checked: status,
        });
    },
});

// good
BaseComponent.extend({
    checkAll: function(status){
        status = !!status;
        this.checkAllList(status);
        this.checkSigList(status);
        this.clearCheckedList();
        if(status){
            this.updateCheckedList();
        }

        this.emitCheckEvent(CHECK_ALL, status);
    },
});
  1. Функции располагаются в порядке зависимости, вызываемая функция должна следовать за вызывающей функцией.
// bad
function updateModule() {}
function updateFilter() {}
function reset() {}
function refresh() {
    updateFilter();
    updateModule();
}

// good
function refresh() {
    updateFilter();
    updateModule();
}
function updateFilter() {}
function updateModule() {}
function reset() {}
  1. Связанные, похожие функции объединены
// bad
function onSubmit() {}
function refresh() {}
function onFilterChange() {}
function reset() {}

// good
function onSubmit() {}
function onFilterChange() {}

function refresh() {}
function reset() {}
  1. объявление переменной рядом с тем местом, где она используется
// bad
function (x){
    var a = 10, b = 100;
    var c, d;

    a = (a-b) * x;
    b = (a-b) / x;
    c = a + b;
    d = c - x;
}

// good
function (x){
    var a = 10, b = 100;

    a = (a-b) * x;
    b = (a-b) / x;

    var c = a + b;
    var d = c - x;
}

5.2 Горизонтальное направление

  1. Пробел между символами операции, но обратите внимание на приоритет операции
// bad
var v = a + (b + c) / d + e * f;

// good
var v = a + (b+c)/d + e*f;
  1. Горизонтальное выравнивание переменных не имеет особого смысла, его следует держать близко
// bad
var a       = 1;
var sku     = goodsInfo.sku;
var goodsId = goodsInfo.goodsId;

// good
var a = 1;
var sku = goodsInfo.sku;
var goodsId = goodsInfo.goodsId;

5.4. Для коротких операторов if и while старайтесь максимально сохранять отступы

Внезапно изменить закон отступа, легко читать привычки.

// bad
if(empty){return;}


// good
if(empty){
    return;
}

// bad
while(cli.readCommand() != -1);
app.run();


// good
while(cli.readCommand() != -1)
;

app.run();

6 Применение в реальном бизнес-коде

Огромная функция конфигурации

Для некоторых более сложных компонентов или компонентов страницы необходимо определить множество свойств, и в то же время эти свойства необходимо инициализировать и отслеживать, как в следующем коде. Я видел похожий код на нескольких больших страницах, в методе config всего 100 строк и целых 400 строк.

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

Component.extend({
    template: tpl,
    config: function(data){
        eu.extend(data, {
            tabChartTab: 0,
            periodType: 0,
            dimensionType: 1,
            dealConstituteCompare:false,
            dealConstituteSort: {
                dimensionValue: 'sales',
                sortType: 0,
            },
            dealConstituteDecorate: {
                noCompare:[],
                progress: ['salesPercent'],
                sort:[
                ]
            },
            defaultMetrics: [
            ],
            // ...下面还有几百行关于其他模块的属性, flow, hotSellRank等
        });

        this.$watch('periodType', function(){
            // ...
        });

        this.$watch('topCategoryId', function(){
            // ...
        });

        // 这里还有一部分异步请求代码...
        this.refresh();
    },
})

Для приведенного выше кода очевидными недостатками являются:

  • слишком долго
  • Переменная снимая избыточная информация, а поиск плохой
  • Переменная (атрибут) слишком много
  • Слишком много всего, инициализация свойств компонентов, добавление мониторов и немного кода бизнес-логики.

Это может сделать некоторые улучшения:

  • Используйте перечисления вместо чисел
  • В конфиге зарезервирован только весь код прямой инициализации атрибутов, увеличивающих область видимости, а остальные атрибуты для модуля будут вызываться вызовомinitDataметод инициализации
  • initDataДалее разделите метод инициализации по модулю
  • Для атрибутов, принадлежащих модулю, они разделены на один и тот же объект, что уменьшает количество атрибутов, монтируемых на компоненте, и упрощает именование атрибутов.
  • Метод наблюдения также заключается вaddWatchersинициализация
  • Часть логики, которая должна быть выполнена в процессе инициализации, должна быть размещена как можно дальшеinitДругие компоненты инициировали выполнение
const TAB_A = 0, TAB_B = 1;
const HOUR = 0, DAY = 1;
const DIMENSION_A = 0, DIMENSION_B = 1;
const DISABLE = false, ENABLE = true;

Component.extend({
    template: tpl,
    config: function(data){
        eu.extend(data, {
            tabChartTab: TAB_A,
            periodType: HOUR,
            dimensionType: DIMENSION_B,
        });

        this.initData();
        this.addWatchers();
    },

    initData: function(){
        this.initDealConsitiuteData();
        this.initFlowData();
        this.initHotSellRank();
    },

    initDealConsitiuteData: function(){
        this.data.dealConstitute = {
            compare: DISABLE,
            sort: {
                dimensionValue: 'sales',
                sortType: 0,
            },
            decorate: {
                noCompare:[],
                progress: ['salesPercent'],
                sort:[
                ]
            },
            defaultMetrics: [
            ],
        }
    },

    addWatchers: function(){
        this.$watch('periodType', function(){
            // ...
        });

        this.$watch('topCategoryId', function(){
            // ...
        });
    },

    init: function(){
        // 部分初始化要执行的逻辑
        this.refresh();
    },

})

На самом деле, после оптимизации по вышеизложенному читабельность кода улучшается, но так как это страничный компонент, то количество строк кода чрезвычайно велико, а модифицированных методов становится больше, что все равно неудобно читать. Поэтому для такой большой страницы правильнее разбить страницу на несколько модулей, разделить бизнес-логику, уменьшить количество кода в каждом модуле и улучшить читаемость. Для компонента или модуля, который нельзя разделить, если он по-прежнему содержит большое количество свойств, которые необходимо инициализировать, приведенный выше пример можно использовать в качестве справки.

7 Резюме

Несколько моментов из этой статьи:

  • Написание кода похоже на написание рассказа: имена различных символов (переменных, функций) в нем должны быть хорошо известны, чтобы можно было бегло читать;
  • Функция должна быть короткой и не смешивать слишком много нерелевантных различных уровней логики;
  • Заметки должны быть краткими и точными, не пишите, если можете;
  • Макет кода должен быть изучен из газет, макет должен обращать внимание на вертикальный и горизонтальный интервалы, а тесно связанный макет должен быть компактным;

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

8 ссылок

  1. Clean Code
  2. clean-code-javascript