Рефакторинг
Алексей Золотых, WrikeClock
#!/bin/sh
npm install -g cloc;
clock master
Рефа́кторинг — процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы
Зачем рефакторить!?
Когда нельзя рефакторить
Необходимость рефакторинга куска кода как функция от частоты изменения
https://goo.gl/56pRGeВсе это не подходит для фронтенда
Все это не очень подходит для фронтенда
Все это не всегда подходит для фронтенда
В браузере много контекстов
Делайте изменения маленькими
К рефакторингу хорошо бы подготовиться
Зачем линтер?!
— код единообразней
Зачем тесты?
Правильные тесты повышают надежность
Код компонента
class FilterForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
frameworks: ['react', 'angular', 'ember', 'backbone']
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const filteredElements = this.state.frameworks
.filter(e => e.includes(this.state.value))
.map(e => { e } )
return (
{ filteredElements }
);
}
}
Код компонента
class FilterForm extends React.Component {
render() {
const filteredElements = this.state.frameworks
.filter(e => e.includes(this.state.value))
.map(e => { e } )
return (
{ filteredElements }
);
}
}
test('should work', () => {
const wrapper = shallow(<FilterForm />);
expect(wrapper.find('li').length).to.equal(4);
wrapper.find('input').simulate('change', {target: {value: 'react'}});
expect(wrapper.find('li').length).to.equal(1);
});
const filteredElements = this.state.frameworks
.filter(e => e.includes(this.state.value))
.map(e => { e } )
const filteredElements = this.state.frameworks
.filter(e => e.includes(this.state.value))
.map(e => <li>{ e }</li>)
test('should work', () => {
const wrapper = shallow(<FilterForm />);
expect(wrapper.find('li').length).to.equal(4);
wrapper.find('input').simulate('change', {target: {value: 'react'}});
expect(wrapper.find('li').length).to.equal(1);
});
test('should work', () => {
const wrapper = shallow(<FilterForm />);
expect(wrapper.find('li').length).to.equal(4);
wrapper.find('input').simulate('change', {target: {value: 'react'}});
expect(wrapper.find('li').length).to.equal(1);
});
Правило туриста
— полянку нужно оставить чище, чем она была
— иногда нужно внедрять принудительно
Иногда помогает мыслить нестандартно
Недостатки
webpack
webpack
Используем консольку
google_closure_compiler *.js | sed 's/\(.*\)/require("\1");/g' > index.js
webpack index.js dist/output.js
А что же стало с глобальными именами?
script-loader
Ваш скрипт выполняется один раз в глобальном контексте
script-loader 💩
expose-loader
Добавляет модуль в глобальный конектст
require("expose-loader?$!jquery");
...
constructor: function(arguments) {
arguments.store = this._escapeValue(arguments.store);
$wspace.task.customfields.ComboBoxField.superclass
.constructor.call(this, arguments);
},
...
...
constructor: function(arguments) {
arguments.store = this._escapeValue(arguments.store);
$wspace.task.customfields.ComboBoxField.superclass
.constructor.call(this, arguments);
},
...
(Абстрактное синтаксическое дерево)
В информатике конечное помеченное ориентированное дерево, в котором внутренние вершины сопоставлены (помечены) с операторами языка программирования, а листья — с соответствующими операндами. Синтаксические деревья используются в парсерах для промежуточного представления
npm install -g grasp
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp 'if.test[op=&&]' a.js
2: if (x && f(x)) { return x; }
4: if (x != j) { return 'test'; }
5: if (xs.length && ys.length) {
10: if (x == 3 && list[x]) {
$ grasp -e 'return __ + __' b.js
3: if (x < 2) { return x + 2; }
13: return '>>' + str.slice(2);
15: return f(z) + x;
$ grasp -e 'return __ + __' b.js
3: if (x < 2) { return x + 2; }
13: return '>>' + str.slice(2);
15: return f(z) + x;
$ grasp -e 'return __ + __' b.js
3: if (x < 2) { return x + 2; }
13: return '>>' + str.slice(2);
15: return f(z) + x;
if (y < 2) {
window.x = y + z;
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (y < 2) {
window.x = y + z;
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (y < 2) {
window.x = y + z;
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (y < 2) {
window.x = y + z;
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (y < 2) {
window.x = y + z;
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (y < 2) {
window.x = y + z;
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (f(y < 2)) {
window.x = f(y + z);
}
$ grasp '[left=#y]' --replace 'f({{}})' f.js
if (f(y < 2)) {
window.x = f(y + z);
}
jscodeshift is a toolkit for running codemods over multiple JS files.
https://github.com/facebook/jscodeshift
module.exports = function(fileInfo, api) {
return api.jscodeshift(fileInfo.source)
.findVariableDeclarators('foo')
.renameTo('bar')
.toSource();
}
module.exports = function(fileInfo, api) {
return api.jscodeshift(fileInfo.source)
.findVariableDeclarators('foo')
.renameTo('bar')
.toSource();
}
module.exports = function(fileInfo, api) {
return api.jscodeshift(fileInfo.source)
.findVariableDeclarators('foo')
.renameTo('bar')
.toSource();
module.exports = function(fileInfo, api) {
return api.jscodeshift(fileInfo.source)
.findVariableDeclarators('foo')
.renameTo('bar')
.toSource();
https://github.com/cpojer/js-codemod
Stylus → CSS + комменатрии
...
color: blue
}
/* $$$ file1.stylus */
.my-awesome-class {
color: red;
....
...
color: blue
}
/* $$$ file1.stylus */
.my-awesome-class {
color: red;
....
Stylus → CSS
Stylus → CSS → PostCSS
POSTCSS + plugins
POSTCSS в сборку
Stylus → CSS → PostCSS
Stylus → CSS → PostCSS → CSS 🔥
Stylus → CSS → PostCSS → CSS 🔥 → Less
Stylus → CSS → PostCSS → CSS 🔥 → Less 😇
gulp.task('refactor', function () {
return gulp.src('folder/**/*.js')
.pipe(RefactoringPlugin())
.pipe(gulp.dest('./'))
})
Если что-то пошло не так, то
$ git reset --hard
$ git merge origin/master
$ gulp refactor
email: aazolotyh@gmail.com