Charla: ¿Qué buenos hábitos debe tener un buen backend?

prefacio

Han pasado casi tres años desde que me gradué, y he trabajado en varias empresas antes y después, y he conocido a varios colegas. He visto todo tipo de código, excelente, basura, poco atractivo, y quiero salir corriendo, etc., por lo que este artículo registra qué buenos hábitos de desarrollo debe tener un excelente desarrollo de back-end en Java.

Estructura de directorio razonable dividida

Afectado por el modelo MVC tradicional, la práctica tradicional es principalmente un controlador de carpetas fijas, servicio, mapeador, entidad y luego agregar ilimitadamente Al final, encontrará que hay docenas o cientos de clases de servicio en una carpeta de servicio. Es imposible distinguir los módulos comerciales en absoluto. La forma correcta es crear una nueva carpeta de módulos en la capa superior del servicio de escritura, crear diferentes paquetes de acuerdo con diferentes negocios en la carpeta moudles y escribir paquetes específicos de servicio, controlador, entidad, enumeraciones debajo de estos paquetes o continuar dividiéndolos.

Cuando la versión de desarrollo se itera en el futuro, si un paquete se puede seguir desmontando, se seguirá desmontando y el módulo empresarial del proyecto se podrá ver claramente. El desmantelamiento posterior de microservicios también es simple.

parámetros del método de encapsulación

Cuando su método tiene demasiados parámetros formales, encapsule un objeto... El siguiente es un material de enseñanza negativo, ¡quién le enseñó a escribir código como este!

public void updateCustomerDeviceAndInstallInfo(long customerId, String channelKey,
                   String androidId, String imei, String gaId,
                   String gcmPushToken, String instanceId) {}
复制代码

escribir un objeto

public class CustomerDeviceRequest {
    private Long customerId;
    //省略属性......
}
复制代码

¿Por qué escribir así? Por ejemplo, su método se usa para consulta. Si agrega una condición de consulta en el futuro, ¿necesita modificar el método? La lista de parámetros del método debe cambiarse cada vez que se agrega. Encapsule un objeto, sin importar cuántas condiciones de consulta agregue en el futuro, solo necesita agregar campos al objeto. ¡Y la clave es que el código también se ve muy cómodo!

Encapsular la lógica empresarial

Si has visto la "montaña de mierda", tendrás un sentimiento profundo. Tal método puede escribir miles de líneas de código, y no hay reglas de las que hablar... A menudo, la persona a cargo dirá que este negocio es También es complicado, no hay forma de mejorarlo, y en realidad es una excusa para la pereza. No importa cuán complejo sea el negocio, podemos usar un diseño y un empaque razonables para mejorar la legibilidad del código. A continuación se muestran dos fragmentos de código escritos por desarrolladores senior (que fingen ser desarrolladores senior)

@Transactional
public ChildOrder submit(Long orderId, OrderSubmitRequest.Shop shop) {
    ChildOrder childOrder = this.generateOrder(shop);
    childOrder.setOrderId(orderId);
    //订单来源 APP/微信小程序
    childOrder.setSource(userService.getOrderSource());
    // 校验优惠券
    orderAdjustmentService.validate(shop.getOrderAdjustments());
    // 订单商品
    orderProductService.add(childOrder, shop);
    // 订单附件
    orderAnnexService.add(childOrder.getId(), shop.getOrderAnnexes());
    // 处理订单地址信息
    processAddress(childOrder, shop);
    // 最后插入订单
    childOrderMapper.insert(childOrder);
    this.updateSkuInventory(shop, childOrder);
    // 发送订单创建事件
    applicationEventPublisher.publishEvent(new ChildOrderCreatedEvent(this, shop, childOrder));
    return childOrder;
}
复制代码
@Transactional
public void clearBills(Long customerId) {
    // 获取清算需要的账单、deposit等信息
    ClearContext context = getClearContext(customerId);
    // 校验金额合法
    checkAmount(context);
    // 判断是否可用优惠券,返回可抵扣金额
    CouponDeductibleResponse deductibleResponse = couponDeducted(context);
    // 清算所有账单
    DepositClearResponse response = clearBills(context);
    // 更新 l_pay_deposit
    lPayDepositService.clear(context.getDeposit(), response);
    // 发送还款对账消息
    repaymentService.sendVerifyBillMessage(customerId, context.getDeposit(), EventName.DEPOSIT_SUCCEED_FLOW_REMINDER);
    // 更新账户余额
    accountService.clear(context, response);
    // 处理清算的优惠券,被用掉或者解绑
    couponService.clear(deductibleResponse);
    // 保存券抵扣记录
    clearCouponDeductService.add(context, deductibleResponse);
}
复制代码

De hecho, el negocio en estos dos códigos es muy complicado. Internamente, se estima que se han hecho 50,000 cosas de manera conservadora, pero la escritura de personas de diferentes niveles es completamente diferente. Tengo que elogiar este comentario, la división de este negocio. y la encapsulación de métodos. Hay varias pequeñas empresas en una gran empresa. Diferentes empresas pueden llamar a diferentes métodos de servicio. Las personas que se hacen cargo pueden comprender rápidamente el negocio, incluso si no tienen documentos relevantes, como diagramas de flujo. Muchos métodos comerciales escritos por desarrolladores principales son los anteriores. la siguiente línea de código es para la empresa A, la siguiente línea de código es para la empresa B y la siguiente línea de código es para la empresa A. También hay un montón de lógica unitaria anidada entre las llamadas comerciales, lo cual es muy confuso y tiene mucha de código

Forma correcta de verificar que un tipo de colección no esté vacío

A muchas personas les gusta escribir código como este para juzgar colecciones.

if (list == null || list.size() == 0) {
  return null;
}
复制代码

Por supuesto, no hay problema si insistes en escribir así... Pero no te sientas incómodo, ahora cualquier paquete jar en el marco tiene una clase de herramienta de colección, como org.springframework.util.CollectionUtils, com. baomidou.mybatisplus.core .toolkit.CollectionUtils. Por favor, escribe así más tarde.

if (CollectionUtils.isEmpty(list) || CollectionUtils.isNotEmpty(list)) {
  return null;
}
复制代码

El valor de retorno del tipo de colección no devuelve nulo

Cuando su método comercial devuelve un tipo de colección, no devuelva nulo, la operación correcta es devolver una colección vacía. Observa la consulta de lista de mybatis.Si no se consulta ningún elemento, devolverá una colección vacía en lugar de nulo. De lo contrario, la persona que llama tiene que hacer un juicio NULL, lo que también es cierto para los objetos en la mayoría de los escenarios.

Trate de no usar tipos básicos para los atributos de la base de datos de mapeo

Todos sabemos que los tipos de datos básicos como int/long son 0 por defecto como variables miembro. Ahora es popular usar marcos ORM como mybatisplus y mybatis Al insertar o actualizar, es fácil insertar y actualizar la base de datos con valores predeterminados. Realmente quiero cortar el desarrollo anterior.Las clases de entidad en el proyecto refactorizado son todos tipos de datos básicos. Roto en el acto...

Condiciones de juicio de embalaje

public void method(LoanAppEntity loanAppEntity, long operatorId) {
  if (LoanAppEntity.LoanAppStatus.OVERDUE != loanAppEntity.getStatus()
          && LoanAppEntity.LoanAppStatus.CURRENT != loanAppEntity.getStatus()
          && LoanAppEntity.LoanAppStatus.GRACE_PERIOD != loanAppEntity.getStatus()) {
    //...
    return;
  }
复制代码

La legibilidad de este código es muy pobre, ¿quién sabe qué hay en este si? ¿Podemos usar el pensamiento orientado a objetos para encapsular un método en el objeto de préstamoApp?

public void method(LoanAppEntity loan, long operatorId) {
  if (!loan.finished()) {
    //...
    return;
  }
复制代码

Un método está encapsulado en la clase LoanApp. En resumen, los detalles de este juicio lógico no deben aparecer en el método comercial.

/**
 * 贷款单是否完成
 */
public boolean finished() {
  return LoanAppEntity.LoanAppStatus.OVERDUE != this.getStatus()
          && LoanAppEntity.LoanAppStatus.CURRENT != this.getStatus()
          && LoanAppEntity.LoanAppStatus.GRACE_PERIOD != this.getStatus();
}
复制代码

Complejidad del método de control

Recomiende un CodeMetrics de complemento de IDEA, puede mostrar la complejidad del método, es para calcular la expresión en el método, la expresión booleana, la bifurcación if/else, el bucle, etc.

Haga clic para ver qué código aumenta la complejidad del método y puede consultarlo adecuadamente. Después de todo, generalmente escribimos código comercial. Lo más importante es permitir que otros lo entiendan rápidamente con la premisa de garantizar un trabajo normal. Cuando la complejidad de su método excede 10, es necesario considerar si se puede optimizar.

Use @ConfigurationProperties en lugar de @Value

De hecho, vi un artículo que recomendaba que @Value es más fácil de usar que @ConfigurationProperties. Lo escupo, no me malinterpreten. Enumere los beneficios de @ConfigurationProperties.

  • En el archivo de configuración application.yml del proyecto, mantenga presionado ctrl + botón izquierdo del mouse y haga clic en la propiedad de configuración para navegar rápidamente a la clase de configuración. Al escribir la configuración, también se puede completar automáticamente y asociar con comentarios. Es necesario introducir una dependencia adicional org.springframework.boot:spring-boot-configuration-processor.

  • @ConfigurationProperties admite la actualización automática de la configuración de NACOS, el uso de @Value requiere la anotación @RefreshScope en BEAN para lograr la actualización automática
  • @ConfigurationProperties se puede combinar con la verificación de validación, @NotNull, @Length y otras anotaciones. Si falla la verificación de la configuración, el programa no se iniciará y los problemas, como la pérdida de la configuración en producción, se descubrirán pronto.
  • @ConfigurationProperties puede inyectar varias propiedades, @Value solo se puede escribir una por una
  • @ConfigurationProperties puede admitir tipos complejos, sin importar cuántos niveles de anidamiento, se pueden asignar correctamente a objetos

Por el contrario, no entiendo por qué tantas personas son reacias a aceptar cosas nuevas, agrietadas... Puede ver que todos los iniciadores de springboot usan @ConfigurationProperties para recibir propiedades de configuración.

se recomienda lombok

Por supuesto, este es un punto discutible, y mi hábito es usarlo para omitir getters, setters, toString, etc.

No llamar a BMapper en AService

Debemos seguir desde AService -> BService -> BMapper.Si cada Servicio puede llamar directamente a otros Mapeadores, ¿por qué necesita otros Servicios? El proyecto anterior también llama al mapeador desde el controlador y trata al controlador como un servicio. . .

Escriba la menor cantidad de herramientas posible

¿Por qué desea escribir menos clases de herramientas, porque la mayoría de las clases de herramientas que escribe están en el paquete jar que introdujo de manera invisible, como String, Assert, archivo de carga de IO, flujo de copia, Bigdecimal, etc. Es fácil escribir sus propios errores y también cargar clases redundantes.

No ajuste los valores de retorno de la interfaz OpenFeign

No entiendo por qué a tanta gente le gusta envolver el valor de retorno de la interfaz en Respuesta... Agregue un código, mensaje y campos de éxito, y luego cada vez que la persona que llama se vuelve así

CouponCommonResult bindResult = couponApi.useCoupon(request.getCustomerId(), order.getLoanId(), coupon.getCode());
if (Objects.isNull(bindResult) || !bindResult.getResult()) {
  throw new AppException(CouponErrorCode.ERR_REC_COUPON_USED_FAILED);
}
复制代码

Esto es equivalente a

  1. Lanzar una excepción en el cupón-api
  2. Interceptar excepciones en el cupón-api y modificar Response.code
  3. La persona que llama juzga el código de respuesta si es FAIELD y luego lanza la excepción...

¿No puede lanzar una excepción directamente al proveedor de servicios? . . Y una solicitud HTTP empaquetada de este tipo siempre es 200, y no hay forma de volver a intentarlo y monitorearlo. Por supuesto, esta pregunta implica cómo diseñar el cuerpo de respuesta de la interfaz.En la actualidad, existen principalmente tres géneros en Internet.

  • El estado de respuesta de la interfaz es siempre 200
  • El estado de respuesta de la interfaz sigue el estado real de HTTP
  • Desarrollo budista, lo que dice el líder es qué hacer

No se aceptan refutaciones, recomiendo usar el estado estándar HTTP. En ciertos escenarios, incluida la falla de verificación de parámetros, se usará 400 para el brindis al frente. El próximo artículo explicará las desventajas del uniforme 200.

Escribir comentarios de métodos significativos

¿Escribiste este tipo de comentario porque tenías miedo de que la persona que se hizo cargo después fuera ciega...

/**
* 请求电话验证
*
* @param credentialNum
* @param callback
* @param param
* @return PhoneVerifyResult
*/
复制代码

O no lo escriba, o simplemente agregue una descripción al final... Me duele escribir un comentario así y recibir un montón de advertencias de IDEA.

Nombre del objeto DTO que interactúa con el front-end

Qué VO, BO, DTO, PO Realmente no creo que sea necesario ser tan detallado. Al menos cuando interactuamos con el front-end, el nombre de la clase debe ser apropiado. No use directamente la clase que mapea la base de datos. para volver al front-end, que devolverá una gran cantidad de información innecesaria, si hay información confidencial, se requiere un tratamiento especial.

La práctica recomendada es que la clase que acepta solicitudes de front-end se defina como XxxRequest y la respuesta se defina como XxxResponse. Tome el pedido como ejemplo: la clase de entidad que acepta y guarda la información actualizada del pedido se puede definir como OrderRequest, la respuesta a la consulta del pedido se define como OrderResponse y la solicitud de condición de consulta del pedido se define como OrderQueryRequest.

Trate de no dejar que IDEA llame a la policía

Me disgusta mucho ver una serie de avisos en la ventana de código de IDEA, muy incómodos. Debido a que hay una advertencia, significa que el código se puede optimizar o que hay un problema. Hace unos días, atrapé un pequeño error dentro del equipo. De hecho, no tenía nada que ver conmigo, pero mis colegas estaban mirando el negocio afuera y juzgaron por qué la sucursal estaba mal. Barrí el problema de un vistazo.

Debido a que los literales enteros en java son de tipo int, se convierte en un entero en la colección, y luego se hace clic en stepId para ver que es de tipo long, que es Long en la colección, luego el contenido devuelve falso, que no es un tipo.

Verá, si presta atención a la advertencia, puede mover el mouse y echar un vistazo al mensaje y será claro, un error de producción menos.

Utilizar componentes de nueva tecnología siempre que sea posible

Creo que esta es la cualidad que debe tener un programador... De todos modos, me gusta usar nuevos componentes técnicos, porque la aparición de nuevos componentes técnicos debe resolver las deficiencias de los componentes técnicos antiguos, y como técnicos debemos estar al día. ~~ Por supuesto, la premisa es hacer preparativos y no actualizar sin pensar. Para dar el ejemplo más simple, Java 17 está disponible y los nuevos proyectos todavía usan Date para manejar la fecha y la hora... ¿A qué edad todavía usa Date?

Epílogo

Este artículo presenta brevemente mis hábitos de desarrollo diario, por supuesto, solo las opiniones del autor. Solo puedo pensar en estos puntos por el momento, y actualizaré los demás más adelante.


Autor: Twilight Enchanting,
Enlace: https://juejin.cn/post/7072252275002966030
Fuente: Rare Earth Nuggets
Los derechos de autor pertenecen al autor. Para reimpresiones comerciales, comuníquese con el autor para obtener autorización, y para reimpresiones no comerciales, indique la fuente.

Supongo que te gusta

Origin blog.csdn.net/wdjnb/article/details/124403326
Recomendado
Clasificación