Artículos de Tecnología

Clean Code: que es, casos de Uso, ejemplo de código limpio

Fernando Furtado
Fernando Furtado

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?

¿Desing de Codigo y el Clean Code?

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.

¿Qué es un código con fácil mantenimiento?

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?

Más allá del mantenimiento

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?

Pruebas, pruebas y más pruebas

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.

Ahora sí ¿que es un código limpio?

Un código limpio es la composición de diversas características, como:

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

Artículos de Tecnología

En Alura encontrarás variados cursos sobre . ¡Comienza ahora!

Precios en:
USD
  • USD
  • BOB
  • CLP
  • COP
  • USD
  • PEN
  • MXN
  • UYU

Semestral

  • 273 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    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

  • Acceso a todo el contenido de la plataforma por 6 meses
US$ 65.90
un solo pago de US$ 65.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Anual

  • 273 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    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

  • Acceso a todo el contenido de la plataforma por 12 meses
US$ 99.90
un solo pago de US$ 99.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Acceso a todos
los cursos

Estudia las 24 horas,
dónde y cuándo quieras

Nuevos cursos
cada semana