SOFTWARE CRAFTMANSHIP - 005

FUNCIONES 


En los inicios de la programación componíamos sistemas con rutinas y sub-rutinas en lo que en ese entonces se conocía como programación lineal, luego, en la era de Fortran y PL/1 nuestros sistemas estaban compuestos de programas, sub-programas y funciones.

Al paso del tiempo solo las funciones sobrevivieron ya que nos permiten aislar tareas especificas y organizar mejor la forma en que leíamos los programas.

Considera el código de mas abajo, Es difícil hallar una larga función en FitNesse (Herramienta de código abierto para pruebas. www.fitnese.org), pero luego de una pequeña búsqueda he encontrado la que ves en el codigo. No solo es larga, sino que también tiene código duplicado, un monton de cadenas (strings) raras y muchos no tan obvios y extraños tipos de datos y APIs (Application Program Interface).

Observa que sentido puedes sacarle en los proximos tres minutos.
public static String testableHtml(
 PageData pageData,
 boolean includeSuiteSetup) throws Exception {

  WikiPage wikiPage = pageData.getWikiPage();
  StringBuffer buffer = new StringBuffer();

  if (pageData.hasAttribute("Test")) {
  
   if (includeSuiteSetup) {
    WikiPage suiteSetup =
    PageCrawlerImpl.getInheritedPage(
    SuiteResponder.SUITE_SETUP_NAME, wikiPage);
  
    if (suiteSetup != null) {
     WikiPagePath pagePath =
     suiteSetup.getPageCrawler().getFullPath(suiteSetup);
     String pagePathName = PathParser.render(pagePath);
     buffer.append("!include -setup .")
     .append(pagePathName)
     .append("\n");
    }
   }

   WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);

   if (setup != null) {
    WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setup);
    String setupPathName = PathParser.render(setupPath);
    buffer.append("!include -setup .").append(setupPathName).append("\n");
   }
  }

 buffer.append(pageData.getContent());

 if (pageData.hasAttribute("Test")) {

  WikiPage teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);

  if (teardown != null) {
   WikiPagePath tearDownPath = wikiPage.getPageCrawler().getFullPath(teardown);
   String tearDownPathName = PathParser.render(tearDownPath);
   buffer.append("\n")
    .append("!include -teardown .")
    .append(tearDownPathName)
    .append("\n");
  }

  if (includeSuiteSetup) {
   WikiPage suiteTeardown =
   PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage);

   if (suiteTeardown != null) {
    WikiPagePath pagePath = suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);
    String pagePathName = PathParser.render(pagePath);
    buffer.append("!include -teardown .")
     .append(pagePathName)
     .append("\n");
   }
  }
 }

 pageData.setContent(buffer.toString());

 return pageData.getHtml();
}

Entendiste la función luego de 3 minutos de estudio? Probablemente No. Hay demasiadas cosas ocurriendo ahí dentro y demasiados niveles de abstracción. Hay cadenas extrañas y funciones raras mezcladas con condiciones "if" anidadas controladas por banderas (flags)!

Sin embargo, con solo algunas extracciones de métodos, uno que otro re nombramiento y una pequeña re estructuración, fui capaz de capturar la intención de la función en once lineas de código. Observa la mejora y dime que puedes entender en 3 minutos?
public static String renderPageWithSetupsAndTeardowns(
 PageData pageData, boolean isSuite) throws Exception {
  
  boolean isTestPage = pageData.hasAttribute("Test");
  
  if (isTestPage) {

   WikiPage testPage = pageData.getWikiPage();
  
   StringBuffer newPageContent = new StringBuffer();
  
   includeSetupPages(testPage, newPageContent, isSuite);
  
   newPageContent.append(pageData.getContent());
  
   includeTeardownPages(testPage, newPageContent, isSuite);
  
   pageData.setContent(newPageContent.toString());
  }

 return pageData.getHtml();
}
A menos que seas un estudiante de FitNesse, probablemente no entiendas todos los detalles. Quizás entiendas que esta función realiza la inclusión de alguna configuración y arroje paginas dentro de una pagina de prueba la cual es rende-rizada en HTML.

Lo importante aquí es como hacer que la función comunique su intención. Que atributos podemos darle a nuestras funciones que le permitan a un lector casual intuir la clase de programa que vive en su interior.

PEQUEÑO!


La primera regla de las funciones es que sean pequeñas. La segunda regla de las funciones es "Que deberían ser menores que eso". Esto no es una afirmación que pueda justificar. No puedo brindar ninguna referencia a una investigación que muestre que las funciones bien pequeñas son mejores. Lo que si puedo decirte es que por mas de 4 décadas he escrito funciones de todos los tamaños, y por mi experiencia y pruebas de prueba y error concluyo que las funciones deberían ser bien pequeñas.
Veamos una re-factorizacion de la función anterior:
public static String renderPageWithSetupsAndTeardowns(
 PageData pageData, boolean isSuite) throws Exception {
 
 if (isTestPage(pageData))
  includeSetupAndTeardownPages(pageData, isSuite);
 
 return pageData.getHtml();
}

BLOQUES e INDENTACION


Esto implica que los bloques de "if", "else", "while" y otros, deberían ocupar solo una linea. Probablemente esa linea de código seria la llamada a una función.

Esto no solo mantiene la función pequeña, pero a la vez le agrega documentación al valor ya que la función a la que se llama podría tener un nombre bien descriptivo.

HAZ UNA COSA (Do One Thing)


Debería ser bien claro que el código de ejemplo inicial hace mucho mas que una sola cosa. Esta creando buffers, cargando paginas, buscando paginas heredadas, rende-rizando rutas, aperturando cadenas y generando HTML, entre otras cosas.

Por otro lado, el código mejorado esta haciendo una simple cosa; esta incluyendo configuraciones y arrojando paginas dentro de paginas de prueba.

El siguiente consejo es legendario desde hace mas de 30 anos:

"LAS FUNCIONES DEBERÍAN HACER UNA SOLA COSA Y HACERLA BIEN"

Lo difícil de esta afirmación es saber la "cosa" que debe hacer bien la función.

Esto nos lleva a describir con pocas palabras la finalidad de la misma, si tomamos los ejemplos anteriores podríamos resumirlo al siguiente párrafo:

"Para RenderizarPaginasYSusConfiguraciones, verifica que cualquiera que sea la pagina, esta es una pagina de pruebas y si lo es, incluimos las configuraciones. En cualquiera de los dos condiciones, rende-rizamos una pagina en HTML"

Si la función hace solo aquellos pasos que están solo a un nivel debajo del estipulado por la función, entonces la función esta haciendo solo una cosa. Después de todo, la razón por la que hacemos funciones es para descomponer un concepto mayor en una serie de pasos que están al siguiente nivel de abstracción.

Comentarios

Publicar un comentario

Entradas populares