miércoles, 26 de febrero de 2014

API cultura

Decíamos ayer...

Aparentemente, este blog está más muerto que la cultura científica en España. Es una pena. De todas maneras, y tomadas en cuenta algunas sugerencias acerca de resucitarlo, y dado que esta es tarea bastante más fácil de llevar a cabo que lo de la cultura científica, he decidido hacer el intento. Pero no prometo nada.

En una entrada anterior mencioné el concepto de API (Application Programming Interface). He comprobado que hay muchos profesionales que no tienen claro que significa. Otros, en cambio, lo que no tienen es ni idea. El conocimiento acerca de qué implicaciones tiene esto en Java resulta deprimente. Intentemos solucionarlo.

Muchos desarrolladores intuyen que el API es algo así como una parte de un producto funcionalmente completo que se utiliza para interactuar con el mismo por programa. Este concepto incluiría cosas como un lenguaje de macros de un procesador de texto, el PL/SQL de un sistema de gestión de bases de datos relacionales, un interfaz o extensión de un producto que permita manejarlo o crear plugins con C o Java... Vale, todo eso es API, pero hay más cosas.

Al pensar en el API de Java, uno tiende a acordarse de la librería estándar. De aquello que aparece en el javadoc como "documentación del API". Con un poco de imaginación, y ya que uno usa MyBatis, Struts y otros frameworks, puede pensar que eso también es API. Lo cierto es que lo es. Pero si buscamos una definición genérica, nos podría valer que el API es el interfaz con el que podemos operar desde nuestro programa con cualquier otra pieza de software, ya sea sistema, producto, framework, librería...

Un momento. Según esto, si yo me dedico a desarrollar en un proyecto cerrado o, dicho de otro modo, si no desarrollo frameworks ni librerías, todo esto no me interesa ¿no? Pues sí. E interesa si simplemente te planteas crear, dentro de tu proyecto, piezas de software reutilizables. No voy a entrar ahora en los beneficios del desarrollo por componentes reutilizables, que no solo implican poder usar algo en más de un proyecto, si no también cambiar una parte de un proyecto por otra sin que se rompa nada. Por no hablar de lo frecuentemente que una parte de un proyecto es utilizada en varios sitios, lo que la convierte en un componente (aunque solo sea por intención). Solo diré que, personalmente, tengo tendencia a diseñar cada pieza de software de un proyecto como componente reutilizable, que me ha ido estupendamente así y que por algo será. Ya discutiremos esto en otro momento. Vamos a quedarnos con que esto del API también me resulta fundamental si me gusta ir por ahí dándomelas de desarrollador de software reutilizable.

Bueno, lo primero que necesitamos saber es, desde el punto de vista de Java, qué es API y que no es API. Para eso necesitamos darle un repaso a los modificadores de accesibilidad. En este punto, el 99% de los esforzados lectores de esto habrán pensado: "¿A mí con éstas, a estas alturas de la película?". Yo contestaría: "Pues sí, a ti con éstas". Una cosa que he podido comprobar con los años es que hay muchos grandes expertos en Java que no tienen claro este tema, aunque sea con el que se abren los cursos de iniciación. Así que le voy a dar un repaso. Total, no tengo otra cosa que hacer en este momento.

Como todos sabemos (supongo) hay cuatro modificadores de acceso. De más a menos limitaciones tenemos:
  • Acceso privado: Lo identificamos con la palabra private y se aplica a miembros de una clase (incluyendo clases anidadas). Todo lo private es accesible solo desde la clase (de primer orden) en que se declara. Vamos bien ¿no?
  • Acceso de paquete: Lo siento, no hay palabra clave, aunque se le suele llamar package-private y se aplica a clases y miembros de clases. Todo lo package-private es accesible desde cualquier clase del mismo paquete en que se declara. Parece bastante claro y que todo el mundo lo sabe. Luego mencionaré un fallo común respecto a esto que me dice que, a veces, no está tan claro. Además, voy a decirlo, aposta, de otra manera: Tiene la misma accesibilidad que el private, extendiéndolo a todas las clases del mismo paquete.
  • Acceso protegido: Con la palabra protected, y aplicable a miembros de clase, proporciona la accesibilidad de package-private extendiéndola a las subclases de la clase en que se ha declarado. ¿Está claro? ¿Seguro? Leedlo de nuevo ¿Sigue claro?... Bueno, ya lo veremos.
  • Acceso público: Con la tan conocida palabra public, palabra que debe ser bonita donde las haya, porque la gente la usa aunque no haga puñetera falta, aplicable a clases y miembros de clase, que proporciona accesibilidad a todo el mundo. O sea, lo mismo que protected, extendido al resto. ¿Lo vamos pillando? Bien.
De acuerdo. Ahora tomemos todo esto y pensemos, por ejemplo, en una librería escrita en Java. Su API, por definición, será todo aquello a lo que tengamos acceso. ¿A qué tenemos acceso en esa librería? Pues, para empezar, a todo lo public. ¿Algo más? Veamos... Podemos tener clases en la librería que podamos extender y que tenga miembros protected, a los cuales tendremos acceso. ¿Algo más? Si hay cosas package-private, siempre puedo usar el truco de crear paquetes con el mismo nombre que los de las librerías y ganar así acceso... Un momento. No creo que esa haya sido la intención del que hizo la librería. Por las mismas, podríamos descompilarla y cambiar los private. No. Eso es hacer trampa.

Así que podemos decir con seguridad que el API de una pieza de software Java está compuesto por todo lo public y todo lo protected. De hecho, se suele conocer a esto como API público.

"Vale. ¿Y a mí qué?"

Vamos a verlo desde otro punto de vista. Cuando yo diseño y desarrollo algo, tengo toda la libertad para hacer y deshacer en la parte que no es de API, llamémosla privada, pero la cosa se me complica en la parte de API. Si yo toco cosas en la parte privada, me las puedo apañar fácilmente (?) para que todo funcione. Si las toco en el API, no sé a quién puedo estar fastidiando. Si este API es solo usado por un proyecto concreto, la cosa puede quedar limitada a cambiar alguna cosa en unos cuantos sitios, o unas decenas, quizá unos cientos (!). En otro caso, un ligero cambio del API podría forzar cambios de otra índole. De profesión, por ejemplo.

¿Quiere esto decir que si publico un API (aunque sea a mis colegas de proyecto), ya no puedo tocarlo por los siglos de los siglos, amén? No exactamente. Siempre puedo ampliarlo y siempre debo respetar sus contratos. Los del API, no los de los colegas.

"¡Ah, vale! Ahora ya lo tengo todo perfectamente claro. Menos por lo de los contratos, que no sé qué es."

Este se me va a hacer un post larguito, parece. Bueno, un contrato es lo que algo garantiza que va a hacer, o no hacer. Para una clase, sus responsabilidades (obsérvese, no "qué hace", estamos en orientación a objetos) y sus invariantes. Para los métodos, sus precondiciones y poscondiciones (que necesitan y, ahora sí, qué hacen). Para campos, qué son. Y, qué suerte, tenemos una muy buena herramienta para conservar y publicar estos contratos: el javadoc. He mencionado en el post de las excepciones algo sobre cómo asegurar los contratos, así que no voy a decir más.

Bien, pues, mientras garanticemos los contratos, somos libres de cambiar lo que queramos. Tocaremos el "cómo hace", no el "qué hace", y todos contentos. Esto en cuanto al API, claro. En cuanto a la parte privada, no hay restricciones.

De aquí se deduce algo muy clarito: cuanto menor y más simple sea el API, mejor podremos mantener el software, porque tendremos la máxima libertad de movimientos. Dicho de otro modo: Colocar en el API algo sin necesidad es una estupidez. Anotemos esto como regla de oro. Por tanto, más nos vale tener un buen diseño de API. Vedlo aquí (Ojo, es larguito y en inglés, pero mola).

"Y ¿cómo hago para decidir qué modificador de accesibilidad coloco cada vez, a falta de que el diseño del API esté claro? Porque mi jefe tiene la sana costumbre de venir cinco minutos antes de la hora de salir el viernes con algo que hay que solucionar inmediatamente porque si no habrá algún tipo de colisión cósmica y la Tierra podría quedar arrasada y esterilizada para los restos (En realidad, es él el que está bajo amenaza de quedar esterilizado por un cierto procedimiento de extirpación traumática)."

Bueno, vamos a hacer algunas suposiciones.

Pongamos que yo, que soy un desarrollador hábil y que gusta de hacer las cosas bien, me veo en la de decidir qué acceso le doy a un método de una clase. Como lo he escrito para ser invocado desde otros sitios de la misma clase, lo hago private. De momento, no me preocupo de nada más. Vamos bien.

En otro momento, porque si fuera el mismo mi problema sería más psicológico que técnico, veo que necesito invocar al método desde otra clase que está en el mismo paquete. Los más avispados verán que estoy subiendo la accesibilidad del método paso a paso. ¿Qué hago? Después de pensarlo un rato, porque hoy no tengo el día fino, voy y me cargo la palabra private. Ya está, no era tan difícil. Quizá me he sentido incómodo por un momento, porque ya no tengo a la vista las invocaciones al método, pero como ahora es package-private, estarán todas ahí mismo.

Más adelante, se me ocurre que me podría venir bien que las subclases de mi clase accedieran a mi método (porque mi clase está pensada para, al menos, tener la posibilidad de ser extendida). Entonces, en un arranque, voy y le coloco el protected al método. Estupendo... ¿Verdad?... ¿Sí?... ¿No?... Un momento... Ahora que caigo, vete a saber quién va a utilizarlo, y cómo. Debo darle un repaso... A ver, empiezo por comprobar las precondiciones y lanzar las excepciones adecuadas... También me aseguro de que las invariantes de clase se cumplan al entrar y al salir, salga como salga... Por supuesto, me aseguro también las poscondiciones, cualquier efecto adicional... Además, como me llega, o devuelvo, algún objeto mutable, voy a asegurarme de que nadie me rompa nada por el procedimiento de la copia defensiva... Creo que ya está. Solo me falta documentar todo esto como es debido utilizando el javadoc, que, por otra parte, está pensado precisamente para estas cosas... Vale, me ha costado más de lo previsto, pero me parece que está bien.

Por último, compruebo que el método guardaría un buen diseño si fuera público. Nada, le cambio protected por public. ¿Algo más? Que ya he salido escaldado de la última... Pues no sé... Parece que no, nada más. ¡Qué cosas!

Vamos a tomar como axioma que para saltarse alguno de los pasos nos vale con aplicar el acumulado de las tareas, y decidiendo que siempre que escribo un método que ya sepa que no va a ser privado, lo hago como si lo fuera y luego aplico lo que necesite. No hace falta tener el CI de Einstein para darse cuenta de que hay un punto en que la cosa se ha complicado. Si trazamos una línea en ese punto nos queda a un lado lo privado y lo de paquete, y al otro lo protegido y lo público. Curiosamente (o no), la división separa precisamente la parte pública (API) y la privada. A esto lo llamo la divisoria o límite del API.

Pero veamos qué pasa si lo hacemos a la inversa.

Mi método es público y decido hacerlo protegido. Nada, cambio public por protected y... ¡Ahí va! ¿Qué son todos esos emails de gente que dice que no le compilan las cosas y que dicen cosas extrañísimas acerca de mis antecesores? Me parece que va a ser que no. No puedo hacer ese cambio así como así. Si estoy trabajando en un componente que sé con seguridad que solo se usa en un proyecto, me tocará, al menos, refactorizar hasta que me dé una artritis, pero aun así es una gaita.

Ahora, mi método protected lo hago de paquete por el conocido procedimiento de borrar el protected. ¡Hala! ¡Otra ristra de emails en similares términos! Tampoco puedo. Si sigo en un componente de un proyecto, de nuevo me toca refactorizar. ¿Qué hago con todas esas cosas que hice para que fuera decente el método al pasar al API? Bueno, el javadoc no me estorba. Las comprobaciones gastan proceso y, total, como solo voy a usar el método desde aquí cerca, no necesito eso, porque ya me encargo yo. Pero como me habían quedado muy chulas y también puedo colarme, porque no soy perfecto aunque me acerque, las sustituyo por asserts.

Para terminar, vuelvo a cambiar el método, que debe tener ya un buen mareo, pasándolo de protected a private. Puede que algo no me compile, pero lo puedo arreglar sobre la marcha. No pasa nada.

¿Qué sacamos de todo esto? Lo primero: cuando ponemos algo en API probablemente vamos a tener que dejarlo así, porque las modificaciones pueden ser muy complicadas, y si no lo son, son imposibles. Así que nos lo pensaremos bien antes hacerlo. Y si lo hacemos, haremos lo que sea necesario, como cuando pasamos de package-private a protected, para que todo se mantenga estable. Segundo: Ante la duda, cuanto más bajo sea el nivel de accesibilidad, mejor, porque subirlo tiene poca complicación, y añadir cosas al API no va a cambiar ningún contrato. El propio diseño de Java nos da pistas sobre esto: ¿Habéis intentado alguna vez sobrescribir un método bajándole el nivel de accesibilidad? El compilador no te deja. Pero no pone ningún problema para subirlo.

¿Y qué pasa con los cambios de funcionalidad y todas esas cosas que nos tocan (?!) frecuentemente en nuestro trabajo? Bueno, si tenemos un javadoc en condiciones, tendremos información sobre el contrato, y podremos ver si nuestro cambio implica algún cambio de contrato. Si es el caso, normalmente podremos jugar con la sobrescritura y la depreciación de métodos y clases para salir del paso sin volver a llenar el buzón de emails vagamente ofensivos (o no demasiado vagamente).

Al hilo de todo esto, se me ocurre una cuestión. Algo que he visto una barbaridad de veces cuando tengo que mantener código que ha hecho alguien en algún momento. Tomemos el siguiente caso.

En un paquete dado, voy a crear una clase abstracta para implementar, pongamos, algún patrón. Pero esta abstracción es privada (no estará en el API), de modo que las subclases estarán en el mismo paquete. Algunos de los métodos de la clase deben ser accedidos por las subclases. ¿Qué accesibilidad les coloco? Normalmente me encuentro con que el desarrollador les ha puesto protected. Es lógico, ¿no? Precisamente protected sirve para eso, para el acceso por parte de las subclases.

Pues no. Está mal. El que lo ha hecho así no ha entendido bien la parte que he explicado antes, cuando dije que protected añadía acceso a package-private. Como las posibles subclases estarán en el paquete, la accesibilidad debe ser package-private. Si no, y si las subclases sí son de API, la sobrescritura del método pasa directamente al API, cosa que, probablemente, no necesitaba ni quería.

Otra cuestión: ¿Para qué -CENSURADO- hace falta que un constructor de una clase abstracta sea public? No se me ocurre ninguna razón. Podría decir lo mismo de los métodos públicos en una clase de paquete, salvo que implementen o sobrescriban alguno que lo sea (ya lo he mencionado, no puedo bajarle la accesibilidad a algo).

Por cierto, los constructores escapan de todo esto, porque un constructor no puede ser sobrescrito. Así, podemos tener un constructor público en una superclase y uno privado con los mismos parámetros en una subclase. Menos mal, porque si no, no podríamos, por ejemplo, instanciar una clase solo por factoría si una superclase tuviera un constructor público. Convendría repasar un poco lo de los constructores, incluyendo los constructores por defecto y las invocaciones a constructores de superclase. En este tema he encontrado muchos fallos y mucho código inútil.

Y tengo una última observación que hacer, sobre el tema de paquetes y subpaquetes en Java. Para empezar, no existe tal cosa, subpaquetes. La relación entre el paquete a.b.c y el a.d.c.e es exactamente la misma que entre a.b.c y x.y.z. Puede que nos venga bien organizar jerárquicamente algunos paquetes para expresar una relación entre ellos. Vale, eso está bien, pero ¿cómo decidimos qué clases van o no en un paquete? Pues por criterios de accesibilidad, o sea, de API. Si colocamos una clase en un paquete siguiendo criterios organizativos y esto implica poner cosas en el API que no tendrían por qué estar ahí, lo estamos haciendo mal.

¿Y si nos sale un paquete con demasiadas clases? Respuesta a la gallega: ¿Cuantas son demasiadas clases? javax.swing tiene casi 150, y no pasa nada con eso, que yo sepa. Y los hay con más. He visto quien recomienda no tener más de 10 clases en un paquete. En un proyecto grande, el API de éste debe ser como para acabar alimentándose de Prozac (especialmente si lo quiere tener documentado). Mi recomendación es ésta, y es aplicable a otras métricas, como líneas de código por método y métodos por clase: Con la complejidad del API a la vista, un paquete debe tener tan pocas clases como sea posible, y tantas como sea necesario. Ahí queda eso.

No hay comentarios:

Publicar un comentario