Aprender a desarrollar y a diseñar software es una tarea en la que hay que invertir muchos años. Hay que ser obsesivo, fanático, hay que equivocarse miles de veces y aprender de esos errores. No contentarse con los aparentes aciertos porque al poco de andar se descubre que no se hizo lo mejor sino que alguien más en un blogs perdido en la web lo resolvió mucho mejor que nosotros y ese alguien también encontrará mejores maneras 10 minutos después de su posteo. Pero sobre todo, hay que leer muchísimo.
En las empresas de desarrollo como mucho existe un 10%-20% de gente con este perfil (atención que hablo de perfil y no de skills) por lo que el restante 90%-80% hará código (y Dios no lo permita, diseños) que van desde extremadamente malo a bastante bueno, mientras que los más experimientados, haran código que va desde lo aceptable a muy bueno con algunos chispazos de excelencia.
El análisis estático de código intenta achicar «un poco» esa brecha haciendo posible la implementacion de estándares y mejores prácticas en la codificación. La idea es extraer conocimiento de los profesionales más experimentados y plasmarlo en un conjunto de reglas que luego guien a la tropa de desarrolladores. En cierto modo esto se logra, porque a cualquiera por más experimentado que sea, una herramienta de análisis estático de código le dirá que su código tiene aspectos a observa, esto es lo mismo que ocurre cuando escribimos 500 u 800 lineas y presionamos F5, el compilador casi siempre nos dice «Eyy! y el punto y coma?» o «Y esta variable no la pensas declarar?» y cosas por el estilo. Sí, el primer análisis estático es el que hace el compilador sobre el arbol de sintaxis. Pero al igual que en el caso del compilador, el nivel análisis que se pueden plasmar en estas herramientas, al menos por ahora, es muy reducido y voy a explicar por qué.
Hace un par de dias, un compañero me preguntó como podia resolver un problema que el «sentía» que no lo estaba resolviendo bien. En una aplicación ASP.NET, tenia un formulario y un conjunto de botones que disparaban distintas acciones sobre los controles del formulario, ejemplo: Clear (limpiaba el formulario), Validate (validaba las entradas), Submit (posteaba el formulario previo tratamiento). El código del método Clear() era similar a este:
1: private void Clear()
2: {
3: foreach (WebControl wctrl in this.Controls)
4: {
5: switch (wctrl.GetType().FullName)
6: {
7: case "System.Web.UI.WebControls.TextBox":
8: ((TextBox)wctrl).Text = String.Empty;
9: break;
10: case "System.Web.UI.WebControls.CheckBox":
11: ((CheckBox)wctrl).Checked = false;
12: break;
13: case "System.Web.UI.WebControls.DropDownList":
14: ((DropDownList)wctrl).SelectedIndex = 0;
15: break;
16: }
17: }
18: }
El problema salta a la vista muy rapidamente, una instrucción switch en cuya expresión interviene una instancia de Type! Esta no es una solución orientada a objetos!. Aquí debe utilizarse un Adapter dije, pero el FxCop no advertía sobre esto. La opción es homogeneizar las operaciones mediante adaptadores:
1: namespace Patterns.Test
2: {
3: public partial class _Default : System.Web.UI.Page
4: {
5: private void Clear()
6: {
7: foreach (WebControl wctrl in this.Controls)
8: {
9: ControlAdapterFactory.Instance.GetAdapter(wctrl).Clear();
10: }
11: }
12: }
13:
14:
15: interface IControlAdapter
16: {
17: void Clear();
18: void Submit();
19: void Validate();
20: }
21:
22: internal class TextBoxAdapter : IControlAdapter
23: {
24: private TextBox textbox;
25:
26: public TextBoxAdapter(TextBox t)
27: {
28: this.textbox = t;
29: }
30:
31: void Clear()
32: {
33: textbox.Text = String.Empty;
34: }
35:
36: void Submit() { }
37: void Validate() { }
38: }
39:
40: internal class ControlAdapterFactory
41: {
42: public static readonly ControlAdapterFactory Instance;
43:
44: private ControlAdapterFactory() { }
45: static ControlAdapterFactory()
46: {
47: Instance = new ControlAdapterFactory();
48: }
49:
50: public IControlAdapter GetAdapter(WebControl wctrl)
51: {
52: IControlAdapter adapter = null;
53: switch (wctrl.GetType().FullName)
54: {
55: case "System.Web.UI.WebControls.TextBox":
56: adapter = new TextBoxAdapter(wctrl);
57: break;
58: case "System.Web.UI.WebControls.CheckBox":
59: adapter = new CheckBoxAdapter(wctrl);
60: break;
61: case "System.Web.UI.WebControls.DropDownList":
62: adapter = new DropDownListAdapter(wctrl);
63: break;
64: default:
65: throw new ArgumentException(
66: String.Format("The control {0} has not an Adapter implemented", wctrl.Name));
67: break;
68: }
69: }
70: }
71: }
Así que pensé: esta regla es muy sencilla de implementar (en este caso no lo fué porque en el switch, cuando intervienen cadenas, no se utiliza el SwitchInstruction sino un infierno de IFs anidados) y me puse a escribir la regla.
Luego se me ocurrió que sería posible identificar patrones de mal diseño (antipatrones de diseño) que pudiesen mapearse con soluciones orientadas o objetos pero pronto llegué a la conclusión de que esto no es posible debido a que el analizador, en este caso una regla de FxCop, debía «entender» primero lo que el código debia hacer para luego sugerir la solución.
Por otro lado, detectar un mal diseño puede ser factible mediante la detección de patrones de mal diseño o bien mediante algunos otros intentos como el de la «complejidad ciclomática» del código la que, amén de los falsos positivos, permite identificar puntos de mejora pero no puede recomendar nada concreto, más allá de un «divida este método en algunos otros». Sin duda este último análisis puede dar una pista que el desarrollador más experimentado puede «considerar» para un análisis pero que pone al desarrollador junior ante un callejón sin salida: «esto está mal». Pero más allá de esto, el problema mayor es que todas estas alternativas llegan tarde, cuando el esfuerzo para el desarrollo de esas piezas de software ya fueron consumidas. Esto puede verse en herramientas que hacen análisis de algunos aspectos de la arquitectura como Klocwork.
Por esto, aún cuando una herramienta pudiese identificar malos mini-diseños y proponer soluciones, no basta con solo una descripción de la solución al estilo «Reemplace este switch con un Adapter y una Factoría» sino que debería tener capacidades de refactorig que asistiese en la tarea.
Ni el análisis estático de código, ni el análisis dinámico de código nos salvan del mal código. ¿Será la programación con pares o las revisiones de código la solución?
Lucas Ontivero