sábado, 28 de agosto de 2010

Ejemplo de arquitectura ligera

Una entrada corta sobre una arquitectura que estoy usando ahora mismo para una aplicación web de tamaño pequeño/mediano. La historia es que un grupo de "modders" estamos haciendo unas modificaciones a un juego multi-jugador online y una de las partes modificadas incluye recoger estadísticas de las partidas que se juegan: información del servidor y mapa que se jugó, qué equipo ganó, cuantas veces mataron y mató cada jugador, cuantos puntos... Y esta información la queremos publicar por web, que es la parte que me toca a mí (mi vena artística no da más que hacer cuatro palotes y mi C está demasiado oxidado como para meter mano al juego).

Así que aquí está lo que uso, por si a alguien le da alguna idea:
  • Como framework-pegamento, uso uno "custom" que es simple y ligero, no necesito más, y por ahí no pienso recomendar nada: es mal negocio :).
  • La interfaz la estoy haciendo principalmente con tablas y elementos de YUI, usando AJAX y JSON para paginar las tablas sin recargar toda la página.
  • Para formatear el JSON y las páginas que hacen de contenedores de los elementos YUI, JSP me basta. Suelo usar otras tecnologías para esto, pero como otros miembros del equipo puede que me ayuden y JSP es lo más común y sencillo...
  • Para hacer más "agradables" las URL y más intuitivas, fácil de recordar y de escribir, pero sin que me condicionen la implementación por debajo: Url Rewrite Filter.
  • Para "decorar" las páginas y hacer que los estilos y menús sean comunes: SiteMesh.
  • Para evitar machacar la BDD con consultas cuando los datos no han cambiado, se usa una cache implementada con OSCache.
  • Para las tareas que comprobarán periódicamente cuando hay que marcar como caducados algunos elementos de la caché (al acabar una partida, por ejemplo, hay que "caducar" todas las estadísticas globales de los jugadores que participaron y la lista general de partidas jugadas) uso Quartz.
  • Para consultar la BDD, como los datos que se muestran son principalmente recopilatorios, las consultas suelen implicar media docena de "joins" y la navegación por entidades mataría el rendimiento, uso MyBatis en lugar de JPA, que es lo que seguramente use para atacar la parte de autenticación, definición de equipos etc. que sigue otra estructura más orientada a objetos.
  • Para no tener que escribir tropecientos getter y setter y dejar el código limpio, uso Lombok para que lo haga por mí sin siquiera tener que verlos en el código. Muy útil en este caso.
  • La BDD es MySQL. No la escogí yo ni hice el esquema así que poco puedo decir excepto que funciona.
  • Para crear, probar y depurar las consultas contra la BDD: Aqua Data Studio.
  • Para no tener que reiniciar el contexto cada vez que hago un cambio en las clases Java: JRebel, aunque tiene algunos conflictos con Lombok. Por otro lado, el framework detecta los cambios en la configuración de MyBatis en ejecución y recarga los "Mapper", así que tengo que re-iniciar el contexto muy muy poco. Y para rizar el rizo, el tiempo de reinicio con este framework ligero y MyBatis no llega a los 4 segundos así que el famoso tiempo perdido esperando a que se (re)inicien las aplicaciones Java en este caso es irrisorio.
  • Como IDE: Eclipse, aunque el proyecto se puede montar solito en base a Ant + Ivy e incluso ejecutarlo, ya que incluye contenedor de servlets embebido para pruebas. No tengo manías y casi cualquier IDE debería valer en este caso, pero uso el que me es más cómodo.
  • Para depurar YUI: El Firefox con Firebug, como no, aunque a veces interfiere con otros plugin y últimamente me tiene algo mosqueado.
  • Para comprobar que el JSON que envío es correcto cuando me da un problema y no se si es por la estructura de JSON o un fallo en JavaScript: JSONLint
  • Como contenedor de servlets para pruebas: Jetty 6, Tomcat 5.5 y Resin 4, en sus formatos "embedido" respectivos y lanzados desde el Ant o a mano. El de producción, que tampoco escojo yo, será Tomcat 5.5, así que estoy cubierto.
De momento las pruebas iniciales son bastante satisfactorias y gracias al uso de paginación a través de AJAX y de cachés en el servidor, parece que aguantaremos la carga. Para este sistema en particular es más importante que la aplicación sea ligera y ágil, que usar grandes frameworks que nos den muchas cosas hechas que no vamos a usar o que nos permitan hacer virguerías que no haremos.

Cada aplicación tiene sus propias cosas e intentar aplicar siempre la misma receta es como intentar cocinar el pollo, el cordero y el cerdo de la misma forma. Algunas veces puede funcionar pero otras es un desastre incomestible. Así que como en las recetas de cocina, si alguien puede aprovechar partes y le sirven para sus circunstancias particulares, me alegro. Si no, pues mala suerte y a buscar sus propias soluciones :).

Bon Appétit & Happy coding!
EJ

lunes, 23 de agosto de 2010

Sobre Gurús y otros falsos mitos

Hoy un tema que seguramente levante algo de polvo, pero para eso estamos al fin y al cabo :).
Lo primero de todo: debo admitir que el "fenómeno gurú" es algo que no va conmigo y que personalmente me produce vergüenza ajena. Lo de elevar a los altares a una persona para considerarla un "gurú" se comprende en quinceañer@s con más hormonas que cerebro, pero que una persona adulta tenga tan poco consideración por si mismo como para sublimar sus opiniones a otra, por el mero hecho de la fama que tiene... en fin, que allá cada cual pero a mí que no me busquen para eso.

Volviendo al tema, una de anécdotas:
Eranse cuatro desarrolladores sentados a una mesa disfrutando de una cena y unas bebidas, dos de los cuales eran gurús de los gordos, de los que su nombre está semana sí, semana no en java.net, Dzone etc. Los otros dos éramos dos "soldados de trinchera", de los que el pan se lo ganan dándole a la tecla, coordinando nuestros proyectos, con nuestras ponencias en congresos de vez en cuando, nuestras colaboraciones en proyectos OS, pero fama: ni de lejos, ni ganas.
Una cena agradable en la que que apareció uno de los temas recurrentes: "para desarrollar me basta con el vi". Los dos gurús argumentando que los hombres de pelo en pecho de verdad sólo necesitan el vi, nosotros argumentando que saber hacerlo a pelo es imprescindible pero que los editores modernos con sus capacidades de refactoring, búsquedas de referencias etc. son una gran ayuda. Ellos que un buen desarrollador tiene todo el proyecto en su cabeza y que igualmente, hackeando el vi y con unas extensiones de no-se-donde hacía lo mismito, y total, ¿Quién necesita refactorizar si basta con escribir todo el código bien de un tirón y a la primera? Mi compañero de trinchera decía que en sus proyectos, donde a veces trabajan hasta seiscientos (sí, tantos) programadores en un sólo proyecto, pues tener formateadores estandarizados, el FindBug, y las capacidades de "refactoring", repositorios de versiones y dependencias integradas etc. sirven para que todo el mundo esté a un nivel parecido y no sólo sirvan los mega-cracks, aparte de hacer que los mega-cracks sean aun más eficientes.
Al final, como en todos estos temas que no tienen demostración matemática, "ni pa' ti, ni pa' mí" si no cada uno con su opinión. Se notó algo que estaban más acostumbrados a que la gente dijera "amén" a sus opiniones por ser quienes eran, pero tampoco tuvieron mucho problema al toparse con dos agnósticos de la fe gurú.
Ahí hubiera quedado la cosa, si no fuera por que al día siguiente en el congreso en el que estábamos le tocaba dar una charla a uno de los dos gurús, el cual ciertamente de su tema sabe un huevo y la yema del otro y mucho más que un servidor. En su charla le tocaba una demostración en vivo de ciertas técnicas y cómo el código podía afectarles, así que tenía su ejemplo preparado que consistía en unas clases Java muy simples para compilar y ejecutar.
La demo empieza bien, ejecuta el código, muestra los resultados que esperaba y todo perfecto.
Entonces toca modificar ligeramente la clase Java para comprobar una de las técnicas. Abre el vi, realiza unas modificaciones, borrando líneas enteras de texto con la x, repitiendo el mismo comando varias veces re-haciéndolo entero, y a la hora de salir guarda con :w y luego sale con :q. Para los que no uséis vi y no lo hayáis pillado, así es como trabaja alguien que sólo conoce los comandos básicos.
Pero bueno, guarda los cambios y compila con javac especificando todo a mano (¿Ant? Eso es para mariquitas). Resultado: No compila. En realidad hace falta modificar también otra clase donde se llama a la primera y como ha cambiado unos parámetros en un método... Eso lo puedo decir yo a 20m leyendo la linea que escupe javac, pero él parece no pillarlo (entiendo que los nervios en escena afectan). Al final alguien se lo chiva y abre el otro fichero, realiza otras modificaciones con la misma "habilidad" con el vi, guarda y sale. A compilar otra vez. Resultado: No compila. Esta vez es por que una de las letras llamando al procedimiento está mal, es mayúscula y ha de ser minúscula, y por eso no funciona. Etc. etc.

Resumiendo: Tuvo que saltarse la demo por que cuando no le fallaba la compilación con el javac, poniendo todos los parámetros a mano, tenía un error tonto en el código y tenía que arreglarlo, lo cual le costaba tiempo por que hasta interpretar los mensajes del javac le costaba en la tensión del momento. Y una cosa que quedó clara como el agua es que no se dedica a ganarse la vida picando código, cosa que yo ya sabía.
Conclusión: Cuando habla de su tema y está en su salsa, le escucho con los ojos abiertos. Cuando habla de otras cosas, como por ejemplo formas de picar código, valoro mucho más mi opinión o la del otro compinche de trincheras que la suya, con todo el respeto y sin desmerecer, que lo cortés no quita lo valiente.

Otra anécdota aun más cortita: Estaba yo en un congreso importante y gracias a un contacto que tengo, importante en el mundillo que no famoso, acabo cenando con un grupo donde hay "un famoso" que ha escrito un libro sobre un tema nuevo de Java, el cual todo el mundo recomienda como la Biblia del saber. Al llegar a la cena el famoso resulta ser un chico joven de veinti-pocos con cara de niño (nada en contra de la juventud, pero uno se imagina a un gurú con una larga barba blanca y profundas arrugas de meditar a la intemperie :) ). Hasta aquí nada reseñable excepto la sorpresa de descubrir una cara tan joven asociada a una tan famosa "fuente de conocimiento". Lo mejor viene durante la cena cuando movido por la curiosidad le preguntó cómo acabó escribiendo el libro ese que le hizo tan famoso. La respuesta es lo que me deja tieso: Acababa de aprender Java y nunca había trabajado con el tema del libro, así que cuando un contacto le propuso escribir un libro sobre el tema, pensó: "así aprenderé de que va esto". O sea, sin quitar méritos al libro que fue una gran guiá de iniciación para muchos, cuando el tío lo empezó a escribir no tenía ni la más remota idea del tema, y no demasiada de Java. Y ésta es la persona cuyas opiniones sobre el tema la gente pone por encima de las demás... en fin serafín.

Por mi parte, todos somos humanos y tenemos cosas de las que sabemos, cosas de las que no. Saber escribir un buen libro para iniciados no significa conocer un tema en profundidad, ser un experto en un tema concreto no nos convierte en expertos en todos los temas y dejarse llevar por la fama de la gente para poner sus opiniones de cualquier cosa por encima de otras, por el mero hecho de ser famosos, es una soplapollez que debería sonrojarnos.

Así que si te encuentras con un experto en un tema y te da su opinión sobre ese tema, escúchale, si te la da sobre cualquier otra cosa, recuerda que a igualdad de "conocimientos" la suya es tan válida como la de cualquiera.

Happy coding!
EJ

sábado, 21 de agosto de 2010

Uso avanzado de Enumeration

No parece que el tema de las cachés interese demasiado, así que mientras decido si continúo o no con el tema, otro consejo breve, en este caso sobre el uso de las enumeraciones en Java.
El uso básico que todo el mundo aprende rápidamente es el de simplemente hacer una lista de elementos para luego, por ejemplo, poderlos pasar como parámetros con verificación de tipos. Algunos desarrolladores, si embargo, se quedan ahí y piensan que su utilidad es limitada puesto que se puede sacar el valor con name(), se puede obtener el índice con ordinal() pero si necesitamos más, tenemos que complicar mucho la cosa... y total para pasar una cadena...
Nada más lejos de la realidad, puesto que extender una enumeración para que nos sea más útil es tremendamente sencillo.
Pongamos un caso parecido a uno con el que trabajé hace poco: Queremos mostrar una lista de empleados de forma ordenada, pudiendo pasar el criterio de ordenación como parámetro, o sea una cadena. Para ordenar una lista de elementos necesitamos un Comparator y no queremos ni que el parámetro a pasar sea un nombre muy largo ni que el nombre de la enumeración sea demasiado corto ni se aparte de las convenciones de código que usamos, así que... ¿como lo podemos hacer?
Muy sencillo:
  • Añadimos dos campos a la enumeración: acrónimo y comparador y los inicializamos al crear cada elemento.
  • Dado que querremos obtener el elemento correcto a partir del acrónimo, no del nombre completo, creamos una tabla de búsquedas inversa, indexada por acrónimos. La creamos estática e inicializada al cargar la clase y siempre la tendremos disponible y preparada, para no tener que buscar en un bucle.
Y el código es igualmente sencillo:
  static Comparator ComparadorPorNombre = new Comparator()
  {
   //...
  };
  static Comparator ComparadorPorSueldo = new Comparator()
  {
   //...
  };
  static Comparator ComparadorPorCargo = new Comparator()
  {
   //...
  };

  public enum Ordenacion
  {
    ORDENAR_POR_NOMBRE(ComparadorPorNombre, "nombre")
    ,ORDENAR_POR_SUELDO(ComparadorPorSueldo, "sueldo")
    ,ORDENAR_POR_CARGO(ComparadorPorCargo, "cargo");
    
    private static Map<String, Ordenacion> mapaAcronimoOrdenacion;

    private final String acronimo;
    private final Comparator comparador;

    private Ordenacion(Comparator comparador, String acronimo)
    {
      this.comparador = comparador;
      this.acronimo = acronimo;
    }

    public Comparator getComparador()
    {
      return comparador;
    }

    public String getAcronimo()
    {
      return acronimo;
    }

    // Método para obtener una ordenación según el acrónimo que nos hayan pasado.
    // Devuelve null si la ordenación no existe.
    public static Ordenacion getPorAcronimo(String acronimo)
    {
      return mapaAcronimoOrdenacion.get(acronimo);
    }

    // Inicializamos el mapa inverso por acronimo al cargar la clase, también
    // podríamos hacerlo de forma "lazy" en el primer acceso, si quisiéramos.
    static
    {
      mapaAcronimoOrdenacion = new HashMap();
      for (Ordenacion rt : Ordenacion.values())
      {
        mapaAcronimoOrdenacion.put(rt.getAcronimo(), rt);
      }
    }
  }

  // Un breve ejemplo de como sería el uso.
  public static void main(String[] arg) throws Exception
  {
    //...
    String param_acronimo = ""; // lo obtenemos de alguna forma
    Ordenacion ord = Ordenacion.getPorAcronimo(param_acronimo);
    Set listaOrdenada = new TreeSet(ord.getComparador());
    //...
  }

De esta forma, podemos añadir nuevas ordenaciones "tranquilamente" añadiendo elementos a la enumeración y podemos saber que en todos los métodos a los que le pasemos una Ordenacion como parámetro seguirán funcionando, y que el control de si existe o no una Ordenacion con ese acrónimo se produce en un punto único, lo cual facilita el mantenimiento.

Lo que he mostrado con un acrónimo y un comparador se puede aplicar a cualquier cosa que se os ocurra: Enumeración con tipos de datos admitidos y expresiones regulares para comprobar si el parámetro es correcto traducciones de acrónimos/códigos numéricos a nombres completos para mostrar al usuario en listas cortas que no estén en BDD...

La cuestión es que si tenéis una lista de elementos y estáis pasando int o String y luego haciendo if/else o switch en varias partes de vuestro código, re-pensadlo a ver si podéis usar una enumeración y hacer el código "type safe" con la traducción de input -> valor correcto en un sólo punto.

Espero que os sirva y...
Happy coding! EJ

viernes, 20 de agosto de 2010

Reflexiones sobre el uso de cachés I

Hoy un par de reflexiones sobre un tema interesante y complejo, aunque menos de lo que parece a primera vista: el uso de cachés* (me disculpo por adelantado del uso incorrecto de memoria caché y sus derivados, pero todos nos entenderemos y no encuentro sinónimo breve y adecuado).
La utilización de cachés es una técnica muy útil para mejorar el rendimiento de nuestras aplicaciones, aunque sufre del mismo tipo de problemas que otras técnicas avanzadas para mejorar el rendimiento, como la programación multi-hilo: Es tremendamente útil pero a la vez peligrosa si no se usa adecuadamente, por lo que mucha gente no se encuentra cómoda usándola y prefiere optar por la prudencia y abstenerse. Sin embargo, en el fondo no es tan complicada de utilizar si sabemos lo que estamos haciendo, así que intentaremos explicar aquí algunos conceptos para ver si ayudan a entenderla mejor.

Qué es y por qué

Lo primero es dejar claro qué es lo que hacemos y por qué lo hacemos: La idea básica es que para obtener el resultado que necesitamos, nuestros programas siguen una serie de pasos que hay que repetir cada vez que pedimos ese resultado. Usar una cache no es más que almacenar alguno de los resultados intermedios para no tener que volver a calcularlo, evitando tener que repetir los pasos que llevan a calcular el resultado intermedio. Así de simple. La razón de hacerlo es aun más sencilla: realizar los pasos cuesta recursos (principalmente tiempo) y si podemos evitar tener que hacerlos, evitamos consumir esos recursos. Esta perogrullada de explicación cobra sentido cuando debamos tomar decisiones sobre si vale la pena usar cachés, dónde, cómo...

Como atacar el problema

Uno de los errores comunes a la hora de implementar técnicas de cachés es empezar pensando cómo vamos a implementar nuestra caché, que librerías utilizar etc. Error. La implementación es importante, pero hay otros factores tanto o más importantes sobre los que merece la pena decidir antes:
  • ¿Merece la pena introducir una caché? Ésta es la primera pregunta que deberíamos hacernos y no es trivial. Volviendo al primer punto, queremos introducir el uso de cachés para obtener unos resultados (ahorrar tiempo, consumo de CPU etc.) y si no lo vamos a conseguir, no merece la pena hacerlo. Aunque no lo parezca, hay muchos tipos de aplicaciones donde el uso de cachés no produce suficientes beneficios para el coste invertido. Para contestar a esta pregunta, es importante estudiar las respuestas a las preguntas que la siguen, ya que nos darán muchas pistas.
  • ¿A que nivel vamos a introducir la caché? En el diseño típico de una aplicación hay varias capas que tendremos que recorrer hasta llegar el resultado que deseamos, y muchas veces el resultado va sufriendo transformaciones por el camino, así que decidir cuál es el resultado intermedio que vamos a cachear es también una de las decisiones importantes.
    Por ejemplo, en una típica aplicación web multi-capa, no es lo mismo guardar el resultado de la consulta a la BDD, que los objetos Java que hayamos construido a partir del resultado de la consulta, que el trozo de JSP (p.e.) donde los hayamos pintado, que toda la página HTML resultado de la petición.
    Hay que pensar que cuanto más cerca estemos del origen del resultado, más fácil será de reutilizar, ya que habrá sufrido menos transformaciones, pero menos estaremos ganando ya que nos ahorraremos menos pasos. Al mismo tiempo, cuanto más complejo sea el resultado que guardemos en caché, más nos ahorraremos pero mayor será la probabilidad de que no nos sirva la próxima vez que lo queramos usar.
    Por ejemplo, pintar la fecha/hora actual en la página resultado, o el nombre del usuario, o tener filtros sobre las búsquedas puede hacer que la página HML entera no la podamos reutilizar para otras peticiones de otros usuarios, pero si bajamos un nivel y guardamos la información antes de añadir esa información personalizada y aplicar el filtro, ahorraremos menos pasos pero podremos reutilizar mucho más ese resultado intermedio.
  • ¿Cómo vamos a mantener actualizada la caché? Esta es la tercera pregunta importante y también nos da una pista muy importante sobre si merece la pena o no introducir una caché. Si hay algo mucho peor que un programa con bajo rendimiento por que no usa cachés, es un programa que devuelve resultados obsoletos por que no actualiza su caché cuando toca. Para poder mantener actualizada adecuadamente nuestra caché necesitamos tener claras dos cosas:
    • Identificar claramente cada elemento que guardamos en la cache para saber cuando lo podemos usar en vez de calcular de nuevo el resultado y cuando no.
    • Cuales son las circunstancias en las cuales el resultado intermedio que tenemos en cachés es inválido y cómo invalidarlo. Por ejemplo, si se actualizan datos en una de nuestras tablas... ¿que objetos de la caché se ven afectados y cómo podemos invalidarlos? ¿Lo haremos por tiempo, usaremos eventos, lo hará el usuario de forma manual?
    No poder responder claramente a estas preguntas o tener una respuesta muy compleja son signos de que quizá usar una caché no sea tan buena idea. Por ejemplo, si actualizar los datos de la tabla X nos invalida toda la caché y esa tabla se actualiza muy a menudo... quizá no nos sirva de nada tener una caché.

  • ¿Cual es el perfil de nuestra aplicación? Una vez visto lo anterior, hay que añadir que no es lo mismo una aplicación donde los datos cambian solamente una vez al año pero el cambio ha de ser visible inmediatamente, que una aplicación donde los datos se actualizan continuamente pero únicamente mostramos resultados consolidados que no cambian. No es lo mismo una aplicación donde el 90% de accesos son a un par de páginas que han de estar siempre actualizadas, que otra donde los accesos están repartidos semi-aleatoriamente entre miles de páginas, aunque cambien poco.
    Siguiendo con los ejemplos, una aplicación como la última mencionada donde se accede aleatoriamente a miles de páginas... o tenemos una caché donde poder tener las miles de páginas pre-calculadas o al no poder guardarlas todas y ser el acceso aleatorio, el uso de la caché puede ser muy bajo así que o nos caben todas en caché o quizá no merezca la pena. En cambio, en la anterior donde un 90% de acceso se producen a un par de páginas que han de estar siempre actualizadas, si montamos un sistema de eventos para tener la caché siempre actualizada podemos sacarle un gran partido a la caché, pero si el sistema para mantenerla actualizada es demasiado complejo e inestable quizá debamos descartar el uso de una caché a ese nivel.

Así pues, el primer paso es responder a estas preguntas y decidir si merece la pena o no implementar una caché. Obviamente habrá muchos casos donde la respuesta no se decantara claramente en un sentido y podemos pasar a hacer pruebas y decidir con datos en la mano, pero como mínimo nos habrá servidor para tener claro cómo queremos hacerlo y qué cosas queremos comprobar en nuestra fase de evaluación.

Hay mucho más que hablar sobre el tema, aunque espero que con esto os de alguna idea de como empezar a estudiar el tema. No quiero hacer este tocho más extenso de lo que es y lo dejaré aquí de momento. Si hay interés, continuaré con el tema explicando algunos perfiles típicos de aplicaciones y las soluciones técnicas más habituales.

Happy coding! EJ

miércoles, 18 de agosto de 2010

TreeSet y java.io.NotSerializableException

Después de un periodo sabático, un truco para solucionar un problemilla habitual por culpa de como está montado el JDK, sin ser realmente culpa de nadie... El problema en sí aparece al tratar de serializar un TreeSet, o cualquier otra colección ordenada, en el cual hemos especificado un Comparator creado como instancia anónima. En código:
static Comparator miComparador =
    new Comparator()
    {
      @Override
      public int compare(Object arg0, Object arg1)
      {
        //...
        return valorAdecuado;
      }
    };

  public static void main(String[] arg)
    throws Exception
    {
      Set miSetOrdenado = new TreeSet(miComparador);
      // ... rellenamos el Set
      ByteArrayOutputStream theBOS = new ByteArrayOutputStream();
      ObjectOutputStream theOOS = new ObjectOutputStream(theBOS);
      theOOS.writeObject(miSetOrdenado);
      theOOS.close();
    }
Si intentamos ejecutar este código, nos saltará una excepción:
java.io.NotSerializableException: test.App$1

Después de volverse tonto mirando por que el los elementos que uno pone en el TreeSet no son serializables, para normalmente descubrir que sí lo son, al final resulta que el problema es el Comparator. Entonces buscamos por Internet y las recomendaciones más habituales son hacer el campo static o transient, implementar Serializable... pero resulta que el Comparator lo tenemos declarado como static, por lo tanto no debería afectar puesto que la clase App sería Serializable... ¿o no? La cuestión sin embargo es... ¿cual es realmente la instancia que no es Serializable?

Para hacer la historia breve, el problema es que la instancia no Serializable es el TreeSet y que el campo que habría que marcar como transient es donde se guarda el Comparator de la clase TreeSet, cosa imposible, así que sólo nos queda declarar que el Comparator implementa Serializable, pero... ¿Cómo lo hacemos si es una instancia anónima? La solución pasa por no usar una instancia de clase anónima y hacer una clase interna, la cual sí podemos declarar que es Serializable y luego crear una instancia. Modificando ligeramente el código:
static class MiClaseComparadora
    implements Comparator, Serializable
  {
    @Override
    public int compare(Object arg0, Object arg1)
    {
      //...
      return valorAdecuado;
    }
  }
  static Comparator miComparador = new MiClaseComparadora();

  public static void main(String[] arg)
    throws Exception
    {
      Set miSetOrdenado = new TreeSet(miComparador);
      // ... rellenamos el Set
      ByteArrayOutputStream theBOS = new ByteArrayOutputStream();
      ObjectOutputStream theOOS = new ObjectOutputStream(theBOS);
      theOOS.writeObject(miSetOrdenado);
      theOOS.close();
    }
Y listo. Un detalle tonto que puede impedir que podamos serializar algunos objetos necesarios para nuestra aplicación y cuyo diagnóstico no es sencillo.

En mi caso, necesitaba almacenar en OSCache una lista ordenada de elementos para no tener que re-ordenarlos cada vez y al pasar la cache de memoria a disco... zas!

Espero que esto le evite a alguien tener que dar tantas vueltas por Internet como a mí, para acabar sin encontrar una solucón clara y concisa.

Happy coding!
EJ