# 先说一下示例怎么运行,先确定本机安装好 node 环境
# 安装项目依赖
npm install
# 首先启动 webpack-dev-server
npm run start-dev
# 上一个运行完毕后不要关闭,开一个新的命令行,启动 node server 服务
npm run start-server
#上述两个启动好后打开浏览器访问 http://localhost:3000 即可
# 跑测试用例
npm test
# 生成测试覆盖报告,跑完后看 coverage 子目录下的内容
npm run test-coverage
# 以上脚本定义都在 package.json 中A medida que las aplicaciones web se vuelven cada vez más complejas, muchas compañías prestan más y más atención a las pruebas unitarias frontales. La mayoría de los tutoriales que vemos hablarán sobre la importancia de las pruebas unitarias y cómo usar algunas API del marco de prueba representativo, pero ¿cómo iniciar las pruebas unitarias en proyectos reales? ¿Qué contenido específico debe incluir los casos de prueba?
Este artículo comienza desde un escenario de aplicación real, analiza qué pruebas unitarias deben contener del patrón de diseño y la estructura del código, y cómo escribir casos de prueba específicos. Espero que puedas ganar algo de los zapatos para niños que ves.
El proyecto utiliza la pila de tecnología react , y los marcos principales utilizados incluyen: react , redux , react-redux , redux-actions , reselect , redux-saga , Antd, inmutable, seamless-immutable antd .

De la capa de UI, este escenario de aplicación consta principalmente de dos partes:
Cuando vea algunos zapatos para niños aquí, puede decir: ¡Corte! ¿Es una interfaz tan simple y una lógica comercial sigue siendo un escenario real? ¿Todavía necesita escribir pruebas unitarias de Shenma?
No se preocupe, para garantizar la experiencia de lectura y la duración del artículo, una escena simple que puede explicar el problema claramente es una buena escena, ¿verdad? Mira hacia abajo lentamente.
En este diseño y desarrollo del escenario, cumplimos estrictamente con las mejores prácticas del flujo de datos unidireccional redux y react-redux , y usamos redux-saga para procesar el flujo comercial, reselect para procesar el caché de estado y las interfaces de backend de llamadas a través de fetch , que no es diferente de los proyectos reales.
El diseño en capas y la organización del código son los siguientes:

Los contenidos en la store intermedia están relacionados con redux , y debe conocer el significado mirando el nombre.
Para códigos específicos, consulte aquí.
Primero hablemos sobre qué marcos y herramientas de prueba se utilizan, el contenido principal incluye:
jest , marco de pruebaenzyme , especializada en la capa de UI de reacción de pruebasinon tiene una biblioteca independiente de falsificaciones, espías, trozos y simulacrosnock , simular el servidor HTTPSi tiene zapatos para niños que no están familiarizados con el uso y la configuración anteriores, solo lea la documentación oficial, que es mejor que cualquier tutorial.
A continuación, comenzamos a escribir un código de caso de prueba específico. Lo siguiente dará fragmentos de código y analizados para cada nivel. Entonces comencemos con actions .
Para hacer que el artículo sea lo más corto y claro posible, los siguientes fragmentos de código no son el contenido completo de cada archivo, y el contenido completo está aquí.
En el negocio, utilicé redux-actions para generar action . Aquí uso la barra de herramientas como ejemplo. Primero veamos un código de negocio:
import { createAction } from 'redux-actions' ;
import * as type from '../types/bizToolbar' ;
export const updateKeywords = createAction ( type . BIZ_TOOLBAR_KEYWORDS_UPDATE ) ;
// ... Para las pruebas actions , verificamos principalmente si el objeto action generado es correcto:
import * as type from '@/store/types/bizToolbar' ;
import * as actions from '@/store/actions/bizToolbar' ;
/* 测试 bizToolbar 相关 actions */
describe ( 'bizToolbar actions' , ( ) => {
/* 测试更新搜索关键字 */
test ( 'should create an action for update keywords' , ( ) => {
// 构建目标 action
const keywords = 'some keywords' ;
const expectedAction = {
type : type . BIZ_TOOLBAR_KEYWORDS_UPDATE ,
payload : keywords
} ;
// 断言 redux-actions 产生的 action 是否正确
expect ( actions . updateKeywords ( keywords ) ) . toEqual ( expectedAction ) ;
} ) ;
// ...
} ) ;La lógica de este caso de prueba es muy simple. Primero, cree un resultado que esperamos, luego llame al código de negocio y finalmente verifique si el resultado de la operación del código comercial es consistente con las expectativas. Esta es la rutina básica de escribir casos de prueba.
Tratamos de mantener el caso de uso de la responsabilidad única al escribir casos de prueba y no cubrir demasiados ámbitos comerciales diferentes. Puede haber muchos casos de prueba, pero cada uno no debe ser complicado.
El siguiente es reducers , que todavía usan handleActions de redux-actions para escribir reducer . Aquí hay una tabla, por ejemplo:
import { handleActions } from 'redux-actions' ;
import Immutable from 'seamless-immutable' ;
import * as type from '../types/bizTable' ;
/* 默认状态 */
export const defaultState = Immutable ( {
loading : false ,
pagination : {
current : 1 ,
pageSize : 15 ,
total : 0
} ,
data : [ ]
} ) ;
export default handleActions (
{
// ...
/* 处理获得数据成功 */
[ type . BIZ_TABLE_GET_RES_SUCCESS ] : ( state , { payload } ) => {
return state . merge (
{
loading : false ,
pagination : { total : payload . total } ,
data : payload . items
} ,
{ deep : true }
) ;
} ,
// ...
} ,
defaultState
) ;El objeto de estado aquí usa
seamless-immutable
Para reducer , principalmente probamos dos aspectos:
action.type , si el estado actual se puede devolver.Aquí están los códigos de prueba para los dos puntos anteriores:
import * as type from '@/store/types/bizTable' ;
import reducer , { defaultState } from '@/store/reducers/bizTable' ;
/* 测试 bizTable reducer */
describe ( 'bizTable reducer' , ( ) => {
/* 测试未指定 state 参数情况下返回当前缺省 state */
test ( 'should return the default state' , ( ) => {
expect ( reducer ( undefined , { type : 'UNKNOWN' } ) ) . toEqual ( defaultState ) ;
} ) ;
// ...
/* 测试处理正常数据结果 */
test ( 'should handle successful data response' , ( ) => {
/* 模拟返回数据结果 */
const payload = {
items : [
{ id : 1 , code : '1' } ,
{ id : 2 , code : '2' }
] ,
total : 2
} ;
/* 期望返回的状态 */
const expectedState = defaultState
. setIn ( [ 'pagination' , 'total' ] , payload . total )
. set ( 'data' , payload . items )
. set ( 'loading' , false ) ;
expect (
reducer ( defaultState , {
type : type . BIZ_TABLE_GET_RES_SUCCESS ,
payload
} )
) . toEqual ( expectedState ) ;
} ) ;
// ...
} ) ;La lógica del caso de prueba aquí también es muy simple, y sigue siendo la rutina de afirmar el resultado esperado anterior. A continuación se muestra la parte de los selectores.
La función del selector es obtener el estado del negocio correspondiente. reselect se usa aquí para almacenar en caché para evitar el recalculación cuando state no se cambia. Primero veamos el código selector de la tabla:
import { createSelector } from 'reselect' ;
import * as defaultSettings from '@/utils/defaultSettingsUtil' ;
// ...
const getBizTableState = ( state ) => state . bizTable ;
export const getBizTable = createSelector ( getBizTableState , ( bizTable ) => {
return bizTable . merge ( {
pagination : defaultSettings . pagination
} , { deep : true } ) ;
} ) ;Los parámetros del buscapersonas aquí se establecen uniformemente en el proyecto, por lo que volver a seleccionar este trabajo bien: si el estado comercial permanece sin cambios, volverá directamente al último caché. La configuración predeterminada del dispositivo de paginación es la siguiente:
export const pagination = {
size : 'small' ,
showTotal : ( total , range ) => ` ${ range [ 0 ] } - ${ range [ 1 ] } / ${ total } ` ,
pageSizeOptions : [ '15' , '25' , '40' , '60' ] ,
showSizeChanger : true ,
showQuickJumper : true
} ;Entonces nuestras pruebas son principalmente dos aspectos:
El código de prueba es el siguiente:
import Immutable from 'seamless-immutable' ;
import { getBizTable } from '@/store/selectors' ;
import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil' ;
/* 测试 bizTable selector */
describe ( 'bizTable selector' , ( ) => {
let state ;
beforeEach ( ( ) => {
state = createState ( ) ;
/* 每个用例执行前重置缓存计算次数 */
getBizTable . resetRecomputations ( ) ;
} ) ;
function createState ( ) {
return Immutable ( {
bizTable : {
loading : false ,
pagination : {
current : 1 ,
pageSize : 15 ,
total : 0
} ,
data : [ ]
}
} ) ;
}
/* 测试返回正确的 bizTable state */
test ( 'should return bizTable state' , ( ) => {
/* 业务状态 ok 的 */
expect ( getBizTable ( state ) ) . toMatchObject ( state . bizTable ) ;
/* 分页默认参数设置 ok 的 */
expect ( getBizTable ( state ) ) . toMatchObject ( {
pagination : defaultSettingsUtil . pagination
} ) ;
} ) ;
/* 测试 selector 缓存是否有效 */
test ( 'check memoization' , ( ) => {
getBizTable ( state ) ;
/* 第一次计算,缓存计算次数为 1 */
expect ( getBizTable . recomputations ( ) ) . toBe ( 1 ) ;
getBizTable ( state ) ;
/* 业务状态不变的情况下,缓存计算次数应该还是 1 */
expect ( getBizTable . recomputations ( ) ) . toBe ( 1 ) ;
const newState = state . setIn ( [ 'bizTable' , 'loading' ] , true ) ;
getBizTable ( newState ) ;
/* 业务状态改变了,缓存计算次数应该是 2 了 */
expect ( getBizTable . recomputations ( ) ) . toBe ( 2 ) ;
} ) ;
} ) ;¿El caso de prueba sigue siendo muy simple? Solo mantén este ritmo. Hablemos de la parte ligeramente complicada, la parte de las sagas.
Aquí uso redux-saga para procesar el flujo comercial, que se llama específicamente API para solicitar datos de forma asincrónica, procesar resultados exitosos y resultados de errores, etc.
Los zapatos de algunos niños pueden pensar que es muy complicado. ¿No se ha terminado usar redux-thunk solicitado asincrónicamente? No se preocupe, lo entenderá después de leerlo pacientemente.
Aquí es necesario introducir brevemente el método de trabajo de redux-saga . SAGA es una función del generador es6 - Generador, que utilizamos para generar varios effects declarativos, que son digeridos y procesados por redux-saga para impulsar los negocios.
Aquí echamos un vistazo al código de negocio para obtener datos de la tabla:
import { all , takeLatest , put , select , call } from 'redux-saga/effects' ;
import * as type from '../types/bizTable' ;
import * as actions from '../actions/bizTable' ;
import { getBizToolbar , getBizTable } from '../selectors' ;
import * as api from '@/services/bizApi' ;
// ...
export function * onGetBizTableData ( ) {
/* 先获取 api 调用需要的参数:关键字、分页信息等 */
const { keywords } = yield select ( getBizToolbar ) ;
const { pagination } = yield select ( getBizTable ) ;
const payload = {
keywords ,
paging : {
skip : ( pagination . current - 1 ) * pagination . pageSize , max : pagination . pageSize
}
} ;
try {
/* 调用 api */
const result = yield call ( api . getBizTableData , payload ) ;
/* 正常返回 */
yield put ( actions . putBizTableDataSuccessResult ( result ) ) ;
} catch ( err ) {
/* 错误返回 */
yield put ( actions . putBizTableDataFailResult ( ) ) ;
}
} Si no está familiarizado con redux-saga no preste demasiada atención a la escritura específica del código. Debe comprender los pasos específicos de este negocio mirando los comentarios:
state correspondiente, y el selector se llama aquí.Entonces, ¿cómo debemos escribir casos de prueba específicos? Todos sabemos que este tipo de código de negocio involucra llamadas de API u otras capas. Si desea escribir pruebas unitarias, debe hacer algunos simulacros para evitar que la capa API realmente se llame. Echemos un vistazo a cómo escribir casos de prueba para esta saga:
import { put , select } from 'redux-saga/effects' ;
// ...
/* 测试获取数据 */
test ( 'request data, check success and fail' , ( ) => {
/* 当前的业务状态 */
const state = {
bizToolbar : {
keywords : 'some keywords'
} ,
bizTable : {
pagination : {
current : 1 ,
pageSize : 15
}
}
} ;
const gen = cloneableGenerator ( saga . onGetBizTableData ) ( ) ;
/* 1. 是否调用了正确的 selector 来获得请求时要发送的参数 */
expect ( gen . next ( ) . value ) . toEqual ( select ( getBizToolbar ) ) ;
expect ( gen . next ( state . bizToolbar ) . value ) . toEqual ( select ( getBizTable ) ) ;
/* 2. 是否调用了 api 层 */
const callEffect = gen . next ( state . bizTable ) . value ;
expect ( callEffect [ 'CALL' ] . fn ) . toBe ( api . getBizTableData ) ;
/* 调用 api 层参数是否传递正确 */
expect ( callEffect [ 'CALL' ] . args [ 0 ] ) . toEqual ( {
keywords : 'some keywords' ,
paging : { skip : 0 , max : 15 }
} ) ;
/* 3. 模拟正确返回分支 */
const successBranch = gen . clone ( ) ;
const successRes = {
items : [
{ id : 1 , code : '1' } ,
{ id : 2 , code : '2' }
] ,
total : 2
} ;
expect ( successBranch . next ( successRes ) . value ) . toEqual (
put ( actions . putBizTableDataSuccessResult ( successRes ) ) ) ;
expect ( successBranch . next ( ) . done ) . toBe ( true ) ;
/* 4. 模拟错误返回分支 */
const failBranch = gen . clone ( ) ;
expect ( failBranch . throw ( new Error ( '模拟产生异常' ) ) . value ) . toEqual (
put ( actions . putBizTableDataFailResult ( ) ) ) ;
expect ( failBranch . next ( ) . done ) . toBe ( true ) ;
} ) ; Este caso de prueba es un poco más complicado que el anterior. Hablemos primero sobre el principio de prueba de saga. Como se mencionó anteriormente, Saga en realidad devuelve varios effects declarativos y luego es ejecutado por el motor. Por lo tanto, el propósito de nuestra prueba es ver si la generación de effects cumple con las expectativas. Entonces, ¿es effect algo mágico? ¡En realidad es un objeto literal!
Podemos generar estos objetos literales de la misma manera en el código de negocios. ¡Las afirmaciones de los objetos literales son muy simples, y no hay necesidad de burlarse sin llamar directamente a la capa API! El paso de este caso de prueba es usar la función del generador para generar el siguiente effect paso a paso, y luego afirmar y comparar.
Como se puede ver en los comentarios 3 y 4 anteriores,
redux-sagatambién proporciona algunas funciones auxiliares para manejar fácilmente los puntos de interrupción de las ramas.
Esta es también la razón por la que elegí redux-saga : es poderoso y propicio para las pruebas.
El siguiente es la capa API relacionada. Como se mencionó anteriormente, fetch las solicitudes de fondo para llamar. Encapsulé dos métodos para simplificar el procesamiento de llamadas y resultados: getJSON() y postJSON() , correspondiente a las solicitudes Get and Post respectivamente. Echemos un vistazo al código de capa API:
import { fetcher } from '@/utils/fetcher' ;
export function getBizTableData ( payload ) {
return fetcher . postJSON ( '/api/biz/get-table' , payload ) ;
}El código de negocio es simple, por lo que los casos de prueba son simples:
import sinon from 'sinon' ;
import { fetcher } from '@/utils/fetcher' ;
import * as api from '@/services/bizApi' ;
/* 测试 bizApi */
describe ( 'bizApi' , ( ) => {
let fetcherStub ;
beforeAll ( ( ) => {
fetcherStub = sinon . stub ( fetcher ) ;
} ) ;
// ...
/* getBizTableData api 应该调用正确的 method 和传递正确的参数 */
test ( 'getBizTableData api should call postJSON with right params of fetcher' , ( ) => {
/* 模拟参数 */
const payload = { a : 1 , b : 2 } ;
api . getBizTableData ( payload ) ;
/* 检查是否调用了工具库 */
expect ( fetcherStub . postJSON . callCount ) . toBe ( 1 ) ;
/* 检查调用参数是否正确 */
expect ( fetcherStub . postJSON . lastCall . calledWith ( '/api/biz/get-table' , payload ) ) . toBe ( true ) ;
} ) ;
} ) ; Dado que la capa API llama directamente a la biblioteca de herramientas, aquí usamos sinon.stub() para reemplazar la biblioteca de herramientas para lograr fines de prueba.
A continuación, pruebe la biblioteca de herramientas de Fetch que encapsuló. Aquí uso isomorphic-fetch , por lo que elegí nock para simular el servidor para probar, probando principalmente los resultados del acceso normal y simulando las excepciones del servidor, etc. El fragmento de ejemplo es el siguiente:
import nock from 'nock' ;
import { fetcher , FetchError } from '@/utils/fetcher' ;
/* 测试 fetcher */
describe ( 'fetcher' , ( ) => {
afterEach ( ( ) => {
nock . cleanAll ( ) ;
} ) ;
afterAll ( ( ) => {
nock . restore ( ) ;
} ) ;
/* 测试 getJSON 获得正常数据 */
test ( 'should get success result' , ( ) => {
nock ( 'http://some' )
. get ( '/test' )
. reply ( 200 , { success : true , result : 'hello, world' } ) ;
return expect ( fetcher . getJSON ( 'http://some/test' ) ) . resolves . toMatch ( / ^hello.+$ / ) ;
} ) ;
// ...
/* 测试 getJSON 捕获 server 大于 400 的异常状态 */
test ( 'should catch server status: 400+' , ( done ) => {
const status = 500 ;
nock ( 'http://some' )
. get ( '/test' )
. reply ( status ) ;
fetcher . getJSON ( 'http://some/test' ) . catch ( ( error ) => {
expect ( error ) . toEqual ( expect . any ( FetchError ) ) ;
expect ( error ) . toHaveProperty ( 'detail' ) ;
expect ( error . detail . status ) . toBe ( status ) ;
done ( ) ;
} ) ;
} ) ;
/* 测试 getJSON 传递正确的 headers 和 query strings */
test ( 'check headers and query string of getJSON()' , ( ) => {
nock ( 'http://some' , {
reqheaders : {
'Accept' : 'application/json' ,
'authorization' : 'Basic Auth'
}
} )
. get ( '/test' )
. query ( { a : '123' , b : 456 } )
. reply ( 200 , { success : true , result : true } ) ;
const headers = new Headers ( ) ;
headers . append ( 'authorization' , 'Basic Auth' ) ;
return expect ( fetcher . getJSON (
'http://some/test' , { a : '123' , b : 456 } , headers ) ) . resolves . toBe ( true ) ;
} ) ;
// ...
} ) ; Básicamente, no hay nada complicado. Lo principal es tener en cuenta que Fetch es un retorno prometedor, y varias soluciones de pruebas asíncronas de jest se pueden cumplir muy bien.
El resto está relacionado con la interfaz de usuario.
El objetivo principal del componente del contenedor es aprobar el estado y las acciones. Mire el código del componente del contenedor en la barra de herramientas:
import { connect } from 'react-redux' ;
import { getBizToolbar } from '@/store/selectors' ;
import * as actions from '@/store/actions/bizToolbar' ;
import BizToolbar from '@/components/BizToolbar' ;
const mapStateToProps = ( state ) => ( {
... getBizToolbar ( state )
} ) ;
const mapDispatchToProps = {
reload : actions . reload ,
updateKeywords : actions . updateKeywords
} ;
export default connect ( mapStateToProps , mapDispatchToProps ) ( BizToolbar ) ; Entonces, el propósito de los casos de prueba también es verificarlos. Aquí usamos redux-mock-store para simular la tienda Redux:
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import BizToolbar from '@/containers/BizToolbar';
/* 测试容器组件 BizToolbar */
describe('BizToolbar container', () => {
const initialState = {
bizToolbar: {
keywords: 'some keywords'
}
};
const mockStore = configureStore();
let store;
let container;
beforeEach(() => {
store = mockStore(initialState);
container = shallow(<BizToolbar store={store}/>);
});
/* 测试 state 到 props 的映射是否正确 */
test('should pass state to props', () => {
const props = container.props();
expect(props).toHaveProperty('keywords', initialState.bizToolbar.keywords);
});
/* 测试 actions 到 props 的映射是否正确 */
test('should pass actions to props', () => {
const props = container.props();
expect(props).toHaveProperty('reload', expect.any(Function));
expect(props).toHaveProperty('updateKeywords', expect.any(Function));
});
});
Es muy simple, así que no hay nada que decir.
Aquí tomamos el componente de la tabla como ejemplo, y veremos directamente cómo se escribe el caso de prueba. En términos generales, probamos principalmente los siguientes aspectos de los componentes de la interfaz de usuario:
Aquí está el código de caso de prueba:
import React from 'react';
import { mount } from 'enzyme';
import sinon from 'sinon';
import { Table } from 'antd';
import * as defaultSettingsUtil from '@/utils/defaultSettingsUtil';
import BizTable from '@/components/BizTable';
/* 测试 UI 组件 BizTable */
describe('BizTable component', () => {
const defaultProps = {
loading: false,
pagination: Object.assign({}, {
current: 1,
pageSize: 15,
total: 2
}, defaultSettingsUtil.pagination),
data: [{id: 1}, {id: 2}],
getData: sinon.fake(),
updateParams: sinon.fake()
};
let defaultWrapper;
beforeEach(() => {
defaultWrapper = mount(<BizTable {...defaultProps}/>);
});
// ...
/* 测试是否渲染了正确的功能子组件 */
test('should render table and pagination', () => {
/* 是否渲染了 Table 组件 */
expect(defaultWrapper.find(Table).exists()).toBe(true);
/* 是否渲染了 分页器 组件,样式是否正确(mini) */
expect(defaultWrapper.find('.ant-table-pagination.mini').exists()).toBe(true);
});
/* 测试首次加载时数据列表为空是否发起加载数据请求 */
test('when componentDidMount and data is empty, should getData', () => {
sinon.spy(BizTable.prototype, 'componentDidMount');
const props = Object.assign({}, defaultProps, {
pagination: Object.assign({}, {
current: 1,
pageSize: 15,
total: 0
}, defaultSettingsUtil.pagination),
data: []
});
const wrapper = mount(<BizTable {...props}/>);
expect(BizTable.prototype.componentDidMount.calledOnce).toBe(true);
expect(props.getData.calledOnce).toBe(true);
BizTable.prototype.componentDidMount.restore();
});
/* 测试 table 翻页后是否正确触发 updateParams */
test('when change pagination of table, should updateParams', () => {
const table = defaultWrapper.find(Table);
table.props().onChange({current: 2, pageSize: 25});
expect(defaultProps.updateParams.lastCall.args[0])
.toEqual({paging: {current: 2, pageSize: 25}});
});
});
Gracias a la racionalidad de la estratificación del diseño, es fácil para nosotros usar props de construcción para lograr fines de prueba. Combinando enzyme y sinon , los casos de prueba aún mantienen un ritmo simple.
Lo anterior son las ideas completas de escritura de casos de prueba y el código de muestra para este escenario. Las ideas y métodos mencionados en el artículo también se pueden utilizar en proyectos Vue y Angular . El contenido completo del código está aquí (diré cosas más importantes, creo que es fácil ayudarme).
Finalmente, podemos usar la tasa de cobertura para ver si el nivel de cobertura del caso de uso es suficiente (generalmente, no es necesario perseguir deliberadamente el 100%, dependiendo de la situación real):

La prueba unitaria es la base del desarrollo basado en pruebas TDD. Desde el proceso anterior, podemos ver que la buena jerarquía de diseño es fácil de escribir casos de prueba, y las pruebas unitarias no son solo para garantizar la calidad del código: ¿lo obligará a pensar sobre la racionalidad del diseño del código y rechazar el código de fideos?
Para pedir prestada la conclusión del código limpio:
En 2005, mientras asistía a la Conferencia de Agilidad en Denver, Elisabeth Hedrickson me entregó una pulsera verde similar a la vendida por Lance Armstrong. La pulsera dice "prueba obsesionada". Lo puse con alegría y con orgullo lo guardé. Realmente he estado obsesionado con el desarrollo impulsado por las pruebas desde que aprendí sobre TDD de Kent Beck en 1999.
Pero algo extraño sucedió. Me encontré incapaz de eliminar la correa. No solo porque la pulsera es muy apretada, sino que también es un hechizo de apriete espiritual. Esa pulsera es una declaración de mi ética de trabajo y un consejo para mi compromiso de hacer todo lo posible para escribir el mejor código. Quítelo como si fuera contra estas declaraciones y promesas.
Entonces todavía está en mi muñeca. Mientras escribía código, lo vi desde el fondo de mi ojo. Me recordaba que hice la promesa de escribir un código ordenado.