Rompe tus cadenas…

Ya lo decía Reincidentes en su mítica canción Carmén:


Y esto no (qué estás esperando)
cambiará (qué estás esperando)
sin luchar (rompe tus cadenas)
pa salir.


Y es que parece mentira como las cadenas y su manipulación vienen siendo un problema desde los tiempos de C. Digo esto por que últimamente me he encontrado con dos usos anómalos de cadenas en código que he visto:

El primer uso anómalo consiste en usar el + y el n o el espacio para escribir cadenas. Solo afecta a la legibilidad y mantenibilidad del código, que no es poco, pero no al rendimiento, ya que la concatenación de cadenas se hará en tiempo de compilación. El código anómalo tendrá un aspecto similar a este:


  const string sql =


        “SELECTn” +


          “DepartmentID,n” +


          “Name,n” +


          “GroupName,n” +


          “ModifiedDaten” +


        “FROMn” +


          “HumanResources.Departmentn” +


        “WHEREn” +


          “ModifiedDate BETWEEN @startDate AND GetDate()n”


El principal problema es que los n o espacios, los + y la gran cantidad de comillas afectan negativamente a la legibilidad de nuestro código. Además la mantenibilidad se ve complicada por el hecho de que si olvidamos uno de los n o espacios finales, en según que puntos, nuestra cadena puede dejar de ser valida. Por ejemplo, si olvidamos el n después del FROM la sentencia SQL no será valida.

Otra variante de este uso anómalo es el uso inadecuado de StringBuilder y AppendLine. Si bien en este caso, los problemas de mantenibilidad son menos, pues no se corre el riesgo de olvidar uno de los espacios finales o de los n, seguimos teniendo problemas de legibilidad y además introducimos problemas de rendimiento, pues la construcción de la cadena completa se hará en tiempo de ejecución y no en tiempo de compilación. En este caso el código anómalo tendrá un aspecto similar a este:


      StringBuilder sql = new StringBuilder();


      sql.AppendLine(“SELECT”);


      sql.AppendLine(”  DepartmentID,”);


      sql.AppendLine(”  Name,”);


      sql.AppendLine(”  GroupName,”);


      sql.AppendLine(”  ModifiedDate”);


      sql.AppendLine(“FROM”);


      sql.AppendLine(”  HumanResources.Department”);


      sql.AppendLine(“WHERE”);


      sql.AppendLine(”  ModifiedDate BETWEEN @startDate AND GetDate()”);


      sql.ToString();


La solución a ambos problemas es la misma, utilizar cadenas verbatim. Utilizando esta característica podemos expresar la cadena de este modo:


      const string sql =


        @”SELECT


            DepartmentID,


            Name,


            GroupName,


            ModifiedDate


          FROM


            HumanResources.Department


          WHERE


            ModifiedDate BETWEEN @startDate AND GetDate()”;


Ahora la pregunta es ¿cúal será la diferencia de rendimiento entre la aproximación que usa el StringBuilder y la aproximación que usa cadenas verbatim?. Para resolver la duda he hecho un pequeño programita (que se encuentra como adjunto a este post), los resultados son clarificadores, en diez millones de iteraciones, la diferencia de tiempos, medida usando System.Diagnostics.StopWatch, es notable: 28 segundos, frente a 12 milisegundos. Podríamos decir que usar cadenas verbatim es unas 2300 veces más eficiente, esto desde luego puede marcar una diferencia notable en algunos excenarios.


La moraleja de la historia es siempre que se usen cadenas estáticas permitir que sea el compilador quien las resuelva, en lugar de usar StringBuilder para componerlas y además usar las cadenas verbatim para mejorar la legibilidad del código.


Recomiendo que le deís un vistazo también a un artículo de mi compañero Jorge Serrano .NET — Rendimiento iterando cadenas.

11 comentarios en “Rompe tus cadenas…”

  1. Rodri,

    Por supuesto la diferencia es abismal, pero es que no es una comparación justa… Como diríamos en Cuba, es “una pelea de león contra mono, y además el mono atado” 🙂

    Con la cadena verbatim (o las concatenaciones del principio), la sentencia SQL (o lo que fuera) queda montada completa en tiempo de compilación, mientras que el StringBuilder la construye en tiempo de ejecución.

    Abrazo – Octavio

  2. Octavio, precisamente ese es el punto en el que quiero incidir, en que siempre que podamos, debemos tratar de que sea el compilador quien resuelva las cadenas en tiempo de compilación y no de ejecución y que además usemos las capacidades de las cadenas verbatim para hacerlas más legibles.

    Evidentemente no es una pelea justa, pero de eso se trata, de darle ventajas a nuestros programas… 😉

    Un saludo titán!!!

  3. Otro tema interesante relacionado con esto y poco utilizado y conocido es la posibilidad de usar el método estático Format de la clase String para componer cadenas dinámicas. Este método permite usar una cadena patrón y rellenar los huecos, tecnica que a menudo es más útil que ir concatenando.

    string.Format(“{0}. Code: {1}”, e.Message, e.ErrorCode);

    Sería equivalente a:

    StringBuilder sb = new StringBuilder();
    sb.Append(e.Message);
    sb.Append(“. Code: “);
    sb.Append(e.ErrorCode);
    sb.ToString();

    En mi opinión da como resultado código más elegante y con menos líneas que usar StringBuilder; y además con la misma eficiencia de ejecución, pues el método Format de la clase string usa un StringBuilder para crear la cadena.

  4. Otra ventaja de las cadenas verbatim con respecto a concatenar cadenas en el código:
    Es mucho más sencillo copiarlas y pegarlas en el SQL Server Management Studio (que no es poca ventaja durante el desarrollo). Bueno, en realidad no es “mucho más sencillo”… sólo “un poco más sencillo” (al menos para quien conoce el poder de las expresiones regulares 😉

  5. En mi practica he utilizado mucho las cadenas verbatim(no sabía que tenían este nombre:-), cuando necesitaba introducir parámetros dentro de la cadena(sobre todo al componer código HTML de salida) utilizaba un replace(no se si el rendimiento queda muy perjudicado pero la legibilidad y el mantenimiento es optimo), el ejemplo es:
    StringBuilder sbHtmlOut=new StringBuilder();
    sbHtmlOut=@”

    @P1

    ”;
    sbHtmlOut.Replace(“@P1”, value)

  6. “… si olvidamos uno de los n o espacios finales, en según que puntos, nuestra cadena puede dejar de ser valida. Por ejemplo, si olvidamos el n después del FROM la sentencia SQL no será valida.”

    A ver a ver a ver… ¿Como es eso de que si no hay un salto de linea despues del FROM, la sentencia no será válida? ¿Eso en qué motor de Bases de datos es? ¿En Firebird version Alpha 0.0.1?

    En la vida he puesto yo un salto de línea después de un FROM en mis Select’s (a no ser para abrir un paréntesis para escribir una sub-consulta, y SIEMPRE como ayuda a la legibilidad), y haya usado el motor que haya usado, han funcionado siempre tan ricamente.

    Así que a lo mejor es que no lo he entendido… 😛

  7. “FROM” + “HumanResources.Departmentn” = “FROMHumanResources.Departmentn”

    Quiza el que use un gestor de Alpha seas tú y no necesites que la clausula FROM tenga un separador (salto de línea, espacio o tabulador) detrás pero vamos a lo mejor son manias de mí gestor de bases de datos…

    Saludos!!!

  8. Creo que pablo se refería a que el hacía esto: “FROM ” + “HumanResources.Department ” = “FROM HumanResources.Department “… y no se necesita los saltos de líneas… pero igual te puedes olvidar los spacios en blanco…

    Rodrigo, para que el compilador sea quien resuelva la cadena verbatim debemos colocar const a la variable?

    Saludos,

  9. Deseo utilizar esta forma de escribir codigo en Visual Basic Net, se podrá.
    Lo he intentado y no me funciona, alguien puede poner un ejemplo en vb.net Gracias.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *