Muchos desarrolladores ya han oído hablar sobre Clean Code, o Código Limpio, y cuando hablamos sobre eso, es común asociarlo con un código de fácil mantenimiento.
Pero, ¿será que Clean Code se refiere solo a un código fácil de mantener?
Si alguna vez has tenido la experiencia de tener que agregar algo relativamente simple en un código existente, y percibiste que esta "agregación simple" impactaría en varios puntos del proyecto, sabes lo que es un código difícil de mantener. Los sistemas heredados no son precisamente el "sueño de un desarrollador" y hay toda una habilidad para lidiar con códigos de software como este, que puede requerir mucho trabajo. Pero no te preocupes, aquí hay una guía para lidiar con softwares heredados. Todo lo que necesitas saber está aquí :)
Sin embargo, si nunca has vivenciado esto, imagina tener que alterar un fragmento de código y darte cuenta de que este cambio simplemente romperá todo el sistema. Definitivamente no sería genial.
Y pasar por esto, hace que pensemos que escribir un código totalmente nuevo es una tarea mucho más simple que hacer mantenimiento en código existente.
Pero, desafortunadamente, en nuestra carrera dedicamos mucho tiempo a mantener el código existente y, si no pensamos adecuadamente en el código que escribimos, vamos a pasar varias veces por situaciones similares a esta.
Recuerda siempre que todo código que escribimos a lo largo del tiempo se convierte en un Pasivo para la empresa. Y cuanto menos nos preocupemos por el mantenimiento del código, mayor es el valor de ese Pasivo.
Cuando hablamos de un código con fácil mantenimiento, nos referimos a un código con Bajo Acoplamiento, Alta Cohesión, utilizando [SOLID](https://kata-software.com/es/publicaciones/principios-solid-en-programacion#:~:text=SOLID es un acrónimo acuñado,eficiente y fácil de mantener.), Objetos inmutables (cuando tiene sentido), aplicando Patrones de Diseño, minimizando Efectos Secundarios, maximizando el uso de Funciones Puras y varias otras cosas.
Todo esto puede ser resumido en tener un buen Desing de Codigo, una parte muy importante para tener código limpio
¿Y qué es lo que mi código necesita tener para ser considerado un código limpio?
Deténgase 1 minuto a revisar este código y intente responder : ¿que hace el?
@Service
public class MovieSessionService {
private MovieSessionRepository sessionRepository;
private UnavailabilityRepository unavailabilityRepository;
private Converter<MovieSessionDTO, MovieSession> converter;
public MovieSessionService(MovieSessionRepository sessionRepository, UnavailabilityRepository unavailabilityRepository, Converter<MovieSessionDTO, MovieSession> converter) {
this.sessionRepository = sessionRepository;
this.unavailabilityRepository = unavailabilityRepository;
this.converter = converter;
}
public Result<MovieSession> create(MovieSessionDTO dto) {
MovieSession session = converter.convert(dto);
List<MovieSession> sessions = sessionRepository.listAllByTheaterId(dto.getTheaterId());
if (sessions.stream().anyMatch(s -> s.getStart().equals(session.getStart()) && s.getEnd().equals(session.getEnd()))) {
return Result.fail(SessionConflictException.class, session);
}
if (sessions.stream().anyMatch(s -> session.getStart().isBefore(s.getStart()) || session.getStart().isAfter(s.getEnd()))) {
return Result.fail(SessionConflictException.class, session);
}
List<Unavailability> unavailabilities = unavailabilityRepository.listAllByTheaterId(dto.getTheaterId());
if (unavailabilities.stream().anyMatch(u -> u.getStart().equals(session.getStart()) && u.getEnd().equals(session.getEnd()))) {
return Result.fail(UnavailablePeriodException.class, session);
}
if (unavailabilities.stream().anyMatch(u -> session.getStart().isBefore(u.getStart()) || session.getStart().isAfter(u.getEnd()))) {
return Result.fail(UnavailablePeriodException.class, session);
}
sessionRepository.save(session);
return Result.success(session);
}
}
¿Qué piensas de esta secuencia de if
? ¿Y este montón de expresiones que se evalúan dentro de cada if
? ¿Cómo podríamos reducir la cantidad de código duplicado?
Te das cuenta que hicimos un esfuerzo muy grande para intentar entender lo que hace este código, y es posible que aún no lo hayamos comprendido.
Este código tiene como función guardar una sesión de cine, siempre y cuando la sesión que estamos intentando guardar no tenga conflictos con otras sesiones existentes ni con una posible indisponibilidad en la sala (por ejemplo, la sala está indisponible por mantenimiento).
Toda esa carga cognitiva que hicimos para tratar de comprender el código resultó en un cansancio físico y mental. Ahora observa que pasamos la mayor parte del tiempo leyendo código.
Así que la legibilidad cuenta mucho cuando estamos escribiendo código.
Por lo tanto, podríamos refactorizar el código a algo como:
@Service
public class MovieSessionService {
private MovieSessionRepository sessionRepository;
private UnavailabilityRepository unavailabilityRepository;
private Converter<MovieSessionRequest, MovieSession> converter;
public MovieSessionService(MovieSessionRepository sessionRepository, UnavailabilityRepository unavailabilityRepository, Converter<MovieSessionRequest, MovieSession> converter) {
this.sessionRepository = sessionRepository;
this.unavailabilityRepository = unavailabilityRepository;
this.converter = converter;
}
public Result<MovieSession> createMovieSessionBy(MovieSessionRequest movieSessionRequest) {
MovieSession newMovieSession = converter.convert(movieSessionRequest);
Result<MovieSession> overlapResult = checkOverlapsWith(newMovieSession);
if (overlapResult.isFail()) {
return overlapResult;
}
sessionRepository.save(newMovieSession);
return Result.success(newMovieSession);
}
private Result<MovieSession> checkOverlapsWith(MovieSession session) {
if (hasOverlapsWithAnotherMovieSessionsBy(session)) {
return Result.fail(SessionConflictException.class, session);
}
if (hasOverlapsWithUnavailabilitiesBy(session)) {
return Result.fail(SessionConflictException.class, session);
}
return Result.success(session);
}
private boolean hasOverlapsWithAnotherMovieSessionsBy(MovieSession session) {
List<MovieSession> sessions = sessionRepository.listAllByTheater(session.getTheater());
return hasOverlapsBetween(sessions, session);
}
private boolean hasOverlapsWithUnavailabilitiesBy(MovieSession session) {
List<Unavailability> unavailabilities = unavailabilityRepository.listAllByTheater(session.getTheater());
return hasOverlapsBetween(unavailabilities, session);
}
private boolean hasOverlapsBetween(List<? extends Periodable> periods, MovieSession session) {
LocalDateTime startTime = session.getStart();
LocalDateTime endTime = session.getEnd();
if (periods.stream().anyMatch(period -> period.getStart().equals(startTime) && period.getEnd().equals(endTime))) {
return true;
}
return periods.stream().anyMatch(period -> startTime.isBefore(period.getStart()) || startTime.isAfter(period.getEnd()));
}
}
El código ahora está más organizado, con algunos nombres mejorados para aumentar la semántica y sin tanta duplicidad. Además, podemos leerlo de arriba a abajo en un flujo continuo.
Podríamos continuar refactorizando el código infinitamente, trasladando las responsabilidades a las clases correctas y así mejorar aún más el Desing del Código y la mantenibilidad.
De esta forma tenemos mucho menos esfuerzo para leer y tratar de comprender el código.
Recuerda que la legibilidad cuenta mucho para un código limpio.
Pero, ¿cómo podemos garantizar que después de estos cambios nuestro código continuará funcionando?
Sí, para garantizar que tu código continúe funcionando, necesitamos escribir pruebas.
Las pruebas hacen parte del juego cuando estamos desarrollando, y el hecho de tenerlas no elimina totalmente la posibilidad de tener un bug, pero minimiza mucho.
Con las pruebas conseguimos garantizar que los escenarios previstos están funcionando y extrapolar estos escenarios es lo que hace que nuestros tests sean más eficientes.
Cuantos más niveles de pruebas (unitarias, de integración, de aceptación, de regresión, etc.) tengamos, más seguridad tenemos al aplicar una refactorización.
La tarea más difícil cuando se escribe una prueba es saber qué debemos probar. Y es precisamente en eso en lo que debemos enfocar nuestro esfuerzo.
Las pruebas son una parte importante de todo el ciclo de vida de desarrollo y, sí, un código limpio es un código testable.
Un código limpio es la composición de diversas características, como:
Legible
El código legible permite la identificación de puntos que necesitan mejorarse. Pasamos más tiempo leyendo código que escribiéndolo, por lo tanto, cuanto más fácil sea de leer el código, menos esfuerzo necesitamos para comprenderlo.
Testable
Debemos probar nuestro código, ya que esto nos proporciona seguridad al alterarlo. Y garantiza que los escenarios que hemos previsto se ajusten a lo esperado.
Fácil de mantener
Nuestro código debe permitir alteraciones tanto para agregar nuevas funcionalidades como para mejorar la legibilidad o mantenibilidad.
De manera muy concisa Un código limpio es un codigo testable, fácil de mantener y de leer.
En Alura, hemos creado una formación en Orientación a Objetos pensando justamente en esta buena práctica.
Fernando Furtado
Cursos de Programación, Front End, Data Science, Innovación y Gestión.
Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana
Paga en moneda local en los siguientes países
Cursos de Programación, Front End, Data Science, Innovación y Gestión.
Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana
Paga en moneda local en los siguientes países
Puedes realizar el pago de tus planes en moneda local en los siguientes países:
País | |||||||
---|---|---|---|---|---|---|---|
Plan Semestral |
487.37
BOB |
68314.51
CLP |
305385.67
COP |
65.90
USD |
265.11
PEN |
1424.44
MXN |
2977.87
UYU |
Plan Anual |
738.82
BOB |
103560.24
CLP |
462944.29
COP |
99.90
USD |
401.89
PEN |
2159.35
MXN |
4514.26
UYU |
Acceso a todos
los cursos
Estudia las 24 horas,
dónde y cuándo quieras
Nuevos cursos
cada semana