Developing Frogtek

El blog del Departamento de Tecnología

Categoría: eficiencia (página 2 de 3)

Excepciones para controlar tu lógica

Hace unos días apareció en nuestro board una User Story que tenía como título:

“Pop-up warning when Cost is higher than Price”

En sus AT’s aparecían las pantallas donde debería hacerse esta comprobación. Así que nos pusimos manos a la obra y estudiamos cuál sería el impacto de este cambio. Vimos que tendríamos que añadir una condición en todas las pantallas y en todos los sitios donde son susceptibles de cambio las dos variables que nos atañen.

Si optábamos por ese camino, esto iba a oler a bug, en el que te reportan que no se muestra el pop-up en un lugar que no tuviste en cuenta a la hora de añadir la condición. Con lo que decidimos no optar por la solución a priori mecánica y sencilla. De repente, la palabra “Excepciones” vino a nuestras mentes, Rubén de Biko2 estuvo durante tres días inculcando su sabiduria en el equipo de Frogtek y algo de lo que nos contó salía a la luz.

Así que decidimos implementarnos una excepción que se lanzase desde la lógica hacia la UI si esta condición se daba. De este modo el impacto sería mucho menor y desde la UI solo tendríamos que controlar dicha excepción y mostrar el pop-up, y además sería imposible saltarnos ningún sitio, puesto que tendríamos error de compilación. Así quedo nuestra clase:

package org.frogtek.tiendatekcore.exceptions;
  public class CostHigherThanPriceException extends Exception {
        public CostHigherThanPriceException() {
            super("Cost is higher than price");
        }
    }

En este caso el mensaje no es importante para nosotros: solo queremos capturar el momento en el que sucede esta condición.

El método del bean que lanzaba la excepción quedaría así:

    public void setPurchaseCost(long purchaseCost) throws CostHigherThanPriceException {
        if (purchaseCost != this.mPurchaseCost) {
            if (this.mPurchasePrice < purchaseCost) {
                throw new CostHigherThanPriceException();
            } else {
                this.mPurchaseCost = purchaseCost;
            }
        }
    }

Después de esto surgieron por la oficina las dudas filosóficas de si estabamos tomando el camino correcto al usar excepciones para esto, o de si un excepción no debería controlar solo errores que no se pueden manejar. Esta es la definición oficial de java:

An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions.

Muy abierta y si os dais cuenta no habla en ningún momento de errores. Habla de eventos.

Conclusiones que en Frogtek hemos obtenido:

  • Que tener a una persona de fuera de la oficina con el espíritu y con las ganas de hacer bien las cosas es increíble.
  • Que usar excepciones es una muy buena estrategia para definir tu lógica. Nosotros somos un equipo de 7 personas tocando y retocando el mismo código a diario. Ayuda mucho que tú tengas que llamar a un método que lance una excepción: desde el primer momento estás advertido de que ese método puede lanzar algo y estás en la obligación de controlarlo.

Carta abierta a una historia de usuario

Estimada Historia de Usuario:

Empezamos mal. Tú y yo sabemos que de estimada nada de nada. Algo de cariño, cierta relación amor-odio, un respeto mutuo inquebrantable… eso sí. Pero de estimarte en Frogtek poco o nada.

Es posible que los gurús del agilismo no comprendan nuestra relación, demasiado liberal quizá, poco comprometida dirán. Me llamarán poco ortodoxo, hereje y hasta libertino. Pero es lo que hay, en Frogtek no te estimamos. Y no me refiero a tus tareas internas o a tus detalles más íntimos, me refiero en general, al objeto que centra la atención de cada uno de nuestros programadores cada día por un periodo indeterminado de entre media hora a cinco días.

Sé que te preguntas qué me has hecho, porqué cuando todas las empresas ágiles debaten interminablemente cual es la mejor manera de estimar una historia de usuario, yo te pago con el desprecio. Ellos te asignan puntos, te dedican tiempo, algunos hasta te compran ropa y tú disfrutas del momento orgullosa, coqueta, probándote las distintas tallas. Y yo nada, pasas discretamente por mi tabla, sin grandes entradas, sin protagonismo, sin que hablen los programadores demasiado de ti hasta que no te llevan al huerto. Aquí te pillo, aquí te mato. Y la salida es peor, si te visto no me acuerdo.  En el fondo no sé de qué te extrañas, todas sois similares y a largo plazo me parecéis todas idénticas. Además mis sprints son largos, así que es mejor para los dos mantener las distancias, es que no sólo sois todas iguales, es que encima sois muchas. Dirás que tengo miedo al compromiso, que para llevar dos años en esto de agilismo soy uno inmaduro, que soy caprichoso e infiel, que soy, en definitiva, un cerdo sólo interesado en coleccionar una historia de usuario tras otra, una muesca más en mi revólver… y algo de razón no te falta.

Hubo un tiempo en el que te estimé, lo sabes. Y no funcionó. Era una relación demasiado absorbente, suponía demasiada dedicación, horas y horas hablando de ti, alimentando tu ego cuando tan sólo habíamos empezado a conocernos y nada sabíamos el uno del otro… me agobié. Lo intenté y fue mal, te estimé y tú siempre te quejabas, te sentías, a veces, sobre-estimada, otras, las más, era todo lo contrario. Así que ya ves, para hacerlo mal, mejor no lo hacemos. Además en Aragón siempre hemos sido más de guiñote.

Ahora me va mejor. Soy más feliz y estoy centrado en lo que realmente me gusta. Os trato a todas por igual y al final del sprint he tenido tiempo para todas vosotras que esperáis desde el principio de cada sprint, para muchas otras que vienen sin avisar y para mi mismo, sin comerme la cabeza. Quizá algún día estime una historia de usuario, quizá algún día le vea el valor a una relación más estable, profunda y comprometida, pero de momento… esto es lo que hay.

Siempre podemos ser amigos.

Atentamente,  Frogtek.

Botones en Android mediante programación

El pasado jueves 7 de abril, la troupe de Frogtek acudimos en comandita al taller organizado por el GTUG Zaragoza e impartido por Francho Joven. La convocatoria fue un éxito y salimos todos muy contentos con lo aprendido y orgullosos de la aplicación que desarrollamos y subimos al market de Android.

Una de las lecciones que vimos fue la de asignar formas (o shapes) creadas mediante programación a elementos como botones. Y yo pensé: ¿por qué no podemos hacer eso mismo con los botones de nuestra aplicación?

Como ejemplo, pongamos que tenemos que añadir un botón de “regresar” típico, que al ser pulsado, vuelva a la pantalla anterior.

Lo primero es conseguir un Button con fondo rojo. Para ello, creamos un layout con un shape (en un fichero shape_red_background.xml) dentro de la carpeta layouts, que nos definirá un tipo de fondo que podremos aplicar a elementos rojos de nuestra aplicación. Le incluiremos un degradado para que quede más molón.



	
	
	
	

Después, queremos asignarle este fondo a nuestro botón, pero como queremos hacerlo reutilizable, vamos a definirlo en un estilo. Para ello, nos creamos un archivo style.xml dentro de nuestra carpeta values (el nombre es lo de menos, lo importante es que esté en values) y añadimos lo siguiente:




Notad que el parámetro android:drawableLeft es para que aparezca una flechica a la izquierda de este botón. Y ahora solo queda añadir este botón a nuestro layout. Para ello basta con:

Bastante limpio, ¿no? Y lo mejor de todo es que si mañana nos piden el mismo botón, pero en color verde, como hemos utilizado herencia de estilos, con hacer un nuevo shape de color verde y crear su style es suficiente:


Pensad ahora la cantidad de espacio que podemos ahorrarnos en nuestras aplicaciones si sustituimos los archivos de imagen por formas creadas mediante programación…

EDIT: de momento, lo único que no he conseguido es darle sombra mediante programación. Existen parámetros shadow_color en Android, pero solo funcionan para elementos de tipo texto.

Google App Engine: Lanzar un MapReduce desde un cron

Con las antiguas limitaciones del GAE, la ejecución de un proceso batch (generación de informes, actualización de una columna en todas las entidades de un tipo…) requería montar complejos sistemas diviendo nuestro proceso en pequeñas partes que serían procesadas por tareas enlazadas.

Desde hace poco (SDK 1.4.0.) el límite de tiempo para las peticiones en segundo plano (tareas y cron) aumentó:  ¡de 30 segundos a 10 minutos! , por lo que,  la mayoría de los procesos antes mencionados podrían ejecutarse sin mayor problema.

Sin embargo, cuando lo que necesitamos es recorrer todas las entidades de un modelo y actualizarlas/operar con ellas, mapreduce nos aporta algunas ventajas:

  • Comodidad a la hora de definir un nuevo script (mapreduce handler).
  • Eficiencia: mapreduce se ejecuta atutomáticamente con un número configurable de shards.
  • Escalabilidad infinita (aquí no tenemos la limitación de 10 minutos en caso de tener millones de entidades).

El problema es que los mapreduce están pensados para lanzarse manualmente desde la consola de administración de mapreduce (url_de_mi_aplicacion/mapreduce) pero en ocasiones nos gustaría poder lanzarlos programáticamente. Estudiando un poco el funcionamiento de esta consola y el código de mapreduce es sencillo modificarlo para conseguir nuestro propósito. Por ejemplo, lanzar un mapreduce desde un cron que se ejecute todos los lunes a la misma hora.

Lo primero es definir la entrada en nuestro cron.yaml, por ejemplo:

cron:
- description: purge tokens
 url: /admin/cron/execute_mapreduce
 schedule: every monday 00:00
 timezone: Europe/Madrid

Nosotros usamos Django, así que definiríamos una entrada en urls.py para mapear la url definida en el cron con nuestro método python encargado de llamar al mapreduce:

(r'^admin/cron/execute_mapreduce$', 'ruta_del_modulo.run_map_reduce'),

Y ahora viene la duda. ¿Cómo llamamos al mapreduce desde dicho método si sólo se puede a través de la consola? Esstudiando el comportamiento de la consola con una herramienta tipo firebug, vemos que cuando se pulsa en el botón “run” se ejecuta una llamada AJAX con una serie de parámetros. Si replicamos dicha llamada en nuestro código ya tenemos el mismo comportamiento.

Si  la definición de nuestro mapreduce (en el mapreduce.yaml) es ésta:

- name: execute_something_from_cron
 mapper:
 input_reader: mapreduce.input_readers.DatastoreInputReader
 handler: map_reduce_handlers.executed_from_cron.execute_something_from_cron
 params:
 - name: entity_kind
 default: points_of_sale.models.pos_model.PointOfSale

Entonces la llamada por código (en localhost) sería esta:

 form_fields = {
 "mapper_handler": "map_reduce_handlers.executed_from_cron.execute_something_from_cron",
 "mapper_input_reader": "mapreduce.input_readers.DatastoreInputReader",
 "mapper_params.entity_kind": "points_of_sale.models.pos_model.PointOfSale",
 "name": "calculate_aggregates_of_aggregates"
 }

 form_data = urllib.urlencode(form_fields)
 result = urlfetch.fetch(url="http://localhost:8080/mapreduce/command/start_job",
 payload=form_data,
 method=urlfetch.POST,
 headers={'Content-Type': 'application/x-www-form-urlencoded'},
 deadline=10)

Por último, para probar que todo funciona ya sólo queda ejecutar el cron. En local se puede hacer a través de la url:

http://localhost:8080/_ah/admin/cron

Pero cuidado, para poder ejecutarlo en local necesitamos un pequeño cambio. Lo normal es que mapreduce requiera permisos de administrador, lo cual, se indica en app.yaml:

- url: /mapreduce(/.*)?
 script: mapreduce/main.py
 login: admin  

Si ejecutamos el cron en la nube no hay problema porque tiene permisos de admin, sin embargo, el test local se ejecuta con permisos de usuario y, por ello, es necesario comentar la línea “login: admin” para las pruebas locales.

Ya parece que tenemos todos lo necesario, así que, lanzamos el test del cron desde el nagegador…obteniendo el siguiente resultado:

ApplicationError: 2 timed out

¿Por qué ocurre esto? La explicación se encuentra en el fichero base_handler.py de la libería mapreduce. Concretamente en el método _handle_wrapper:

def _handle_wrapper(self):
 if self.request.headers.get("X-Requested-With") != "XMLHttpRequest":
 logging.error(self.request.headers)
 logging.error("Got JSON request with no X-Requested-With header")
 self.response.set_status(
 403, message="Got JSON request with no X-Requested-With header")
 return

Como vemos, se están vetando explícitamente aquellas peticiones que no sean AJAX.

Afortunadamente, existe una solución muy fácil que evita cambiar el código de la librería mapreduce (sería peligroso porque en cualquier momento puede cambiar de versión o incorporarse por defecto sin necesidad de incluirla en el despliegue): se trata de añadir la cabecera XMLHttpRequest a nuestra petición:

headers={'Content-Type': 'application/x-www-form-urlencoded',
 'X-Requested-With': "XMLHttpRequest"},

Y ahora ya podemos ejecutar el mapreduce desde el cron y consultar la consola/logs cuando queramos ver su resultado. También se podrían lanzar peticiones (al igual que lo hace la consola) para comprobar que todo ha ido bien por código, pero eso ya es otra historia…

Utilizando gzip en las comunicaciones

Como ya sabéis hace dos semanas estuvimos de ranatón. En mi equipo, uno de nuestros objetivos era optimizar las comunicaciones entre el servidor y el cliente. El objetivo era optimizar todo lo que no habíamos optimizado todavía, ya que, en Colombia (donde están la mayoría de nuestros usuarios), las tarifas de datos aún son muy restrictivas en cuanto a datos enviados/recibidos.

La comunicación actualmente se estaba realizando usando Json y en texto plano.

Tal como este:

{"D":1,"sale_code":"301","type":"sale",
"local_created":"2011-01-05 09:26:57.042","balance_sheet_code":"501",
"customer_code":"-1","products":{"generic1":{"Q":1,"P":300,"F":1}},
"final_price":300,"paid":1,"cash_operation_code":"401"}

En este ejemplo estabamos enviando una operación de tipo venta. Como primera optimización, decidimos acortar los nombres de las propiedades. De este modo nos quedó:

{"D":1,"SC":"301","T":"sale","LC":"2011-01-05 09:26:57.042",
"BS":"501","CC":"-1","PR":{"generic1":{"Q":1,"P":300,"F":1}},
"FP":300,"PD":1,"CO":"401"}

Así conseguimos reducir bastante el tráfico de datos, pero necesitabamos reducirlo todavía más, así que tomamos la decisión de utilizar la compresión gzip, y comprimir todo el texto enviado y recibido por el cliente. Y después de comprimirlo lo convertimos en una string con BASE64, de este modo formateabamos los datos sin retornos de carro y todo quedaba, si cabe, mas uniforme:

H4sIAAAAAAAAADWOzQoCMQyEX0Xm3JW03SrmaMWT
aHU9KR5EiwiyygpeSt/dtCIE8s3kN+H5AuMwrK3toXCNH5GkBT
fgY8ICrBU6L66t9l7ofX5EwVVxDWndkIQb0YzNhN10TK2R8ryT
sqtDvnQ2hcIOnHCLfRzuF114Wy8EsCVSWIrKWfLfCL8PvLyDVpb
lU/4C8o/at7UAAAA=

No es el caso más optimo para comprimir, pero hemos calculado que enviamos un 50% menos de datos al enviar la información comprimida. Para poder medir esto utilizamos WireShark con un filtro tal como este (Gracias Jose):

(ip.src == 192.168.1.9  &&
           ip.dst == 192.168.1.12 &&
           tcp.port == 8080)

Para poder realizar la compresión en el cliente Android utilizamos la clase GZIPOutputStream de este modo:

public static byte[] zipStringToBytes( String input  ) throws IOException
  {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    BufferedOutputStream bufos = new BufferedOutputStream(new GZIPOutputStream(bos));
    bufos.write( input.getBytes() );
    bufos.close();
    byte[] retval= bos.toByteArray();
    bos.close();
    return retval;
  }

Y en la parte del servidor, nosotros utilizamos Google App Engine:

def decode_client_data(request_object):
    if(request_object.META.get('HTTP_CONTENT_ENCODING',None)!=None):
        gzip_string = base64.standard_b64decode(str(request_object.raw_post_data))
        url_file_handle=StringIO(gzip_string)
        gzip_file_handle = gzip.GzipFile(fileobj=url_file_handle)
        decompressed_data = gzip_file_handle.read()
        gzip_file_handle.close()
        return decompressed_data
    else:
        return request_object.raw_post_data

Gracias a la integración continua podemos asegurarnos de que ningún cliente trabajará contra una servidor que no acepte gzip, pero para evitarnos problemas y poder sacar versiones a producción del servidor sin tener que hacerlo del cliente, decidimos programar el servidor para que acepte operaciones sin comprimir y en versión de nombres de propiedades largas.

Para que el servidor nos envie la información comprimida en gzip es mucho más fácil, puesto que lo hace de manera automática siempre y cuando en las peticiones le digamos que nuestro cliente acepta compresión gzip. Nosotros lo hemos hecho de este modo:

HttpPost httppost = connectPOST(url);
   httppost.setHeader("Accept-Encoding", "gzip");
   httppost.setHeader("Content-Encoding", "GZIP");
   httppost.setHeader("User-Agent", "gzip");

Hay que tener muy presente que el gzip automático por parte de GAE no funciona en local.

Si tenéis alguna duda o sugerencia no dudéis en comentarla.

¿Qué IDE usas?

Hoy en frogtek vamos a tener nuestra primera kata organizada por Jose. Ayer mientras nos enviaba información acerca de las webs que había visitado para prepararla, pensé en el IDE y de qué manera un developer se siente cómodo al usarlo.

Yo desde hace un tiempo llevo usando NetBeans para desarrollar en Android y GAE, me cansé de la inestabilidad y lentitud de Eclipse cuando tratas con varios proyectos grandes.

Esto también me hizo pensar acerca de la ayudas que un IDE proporciona a un programador, como por ejemplo:

  • Completar métodos.
  • Compilación inmediata.
  • Sugerencias de corrección de errores.

Todas estas ayudas que en principio están diseñadas para hacernos la vida más fácil y aumentar la rapidez de desarrollo, pueden convertirse en un lastre en cuanto a eficiencia se refiere y una frustración diaria. Todo esto relacionado con que debemos tender a “Go Mouseless”, para aumentar nuestra productividad, me hace pensar que cada día deberíamos bajar un escalón en la abstracción e intentar obviar las ayudas en busca de más rendimiento. También creo que estas ayudas a la larga hacen perder un poco la visión global de proyectos grandes.

Me gustaría saber cuáles son vuestros IDE’s mas usados y vuestras configuraciones.

El Pomodoro Síncrono ya está aquí

Ayer tuvimos una reunión de retrospectiva. Solemos hacer una al mes aproximadamente. Ésta fue particularmente interesante, especialmente por Jose, Linares y Pedro que habían acudido el fin de semana anterior al Agile Open Spain de Barcelona y se trajeron feed-back fresquito e interesantes ideas para aplicar en nuestro entorno de trabajo.

Esta semana presentamos… ¡el Pomodoro Síncrono! (lo que hay que oir). Se trata de buscar maneras de aumentar la productividad y se puede aplicar tanto a programadores como a cualquier otra persona que necesite cierta tranquilidad y concentración para llevar a cabo su trabajo. Básicamente la idea del Pomodoro consiste en reservar espacios de tiempo para aislarse del entorno y concentrarse un 200% en una tarea. Está claro, si apagas el teléfono, el chat, el twitter, el facebook, haces voto de silencio y te pones unos cascos tu productividad aumenta varios órdenes de magnitud. La duración de dichos espacios de tiempo es variable y personal, hay quien es capaz de aguantar en una especie de estado entre el trance y el nirvana durante varias horas seguidas, hay quien con media hora de aislamiento sensorial tiene más que suficiente. Resulta obvio que no es una técnica de la que se deba abusar pero también que, cada vez más y sobre todo en un trabajo como el de programador, si se quiere aumentar la productividad (esto es, ser más rápido, cometer menos errores y acabar más cosas) hay que ser capaces de abstraerse del ruido, principalmente spam cibernético, que nos rodea.

¿Y por qué Pomodoro?. Porque el tío que lo inventó usaba un reloj de cocina con forma de Tomate. ¿Y por qué Síncrono?. Porque si lo práctica toda la oficina a la vez es mucho más efectivo. Así que hemos decidido hacer Pomodoro Síncrono todos los días:

  • de 9:30 a 10:30
  • de 12:00 a 13:00
  • de 16:00 a 17:00

Desgraciadamente no hemos encontrado un reloj-tomate… así que siempre nos quedará el consuelo de decir: “Hoy he currado un huevo”.     😛

Crear nuestros propios adapters para listados, y no morir en el scroll

Si no vienes del post introductorio, quizás te gustaría leerlo.

Tercera entrega de la optimización a la que fue sometida nuestra aplicación. Esta vez, tiene que ver con los listados de elementos; de muchos elementos, como puede ser el inventario de una tienda. Sirva como ejemplo práctico que solíamos crear nuestros propios adapters (extend BaseAdapter, todo sea dicho) muy alegremente, pero sólo al principio. Como buenos desarrolladores, copiábamos código del primer sitio que encontrábamos, sin fijarnos en lo óptimo que era. Y como buenos desarrolladores, repito, hacíamos las pruebas de nuestros propios adapters con 5 o 6 elementos para simular el inventario de una tienda.
¿Qué pasó? Cuando tuvimos nuestro primer tendero con ganas de inventariar toda su tienda, enseguida se nos vio el plumero. Una lista de 1200 (mil doscientos) elementos, con código copi-pegado y poco probado, tiene un 99% de posibilidades de ir más lenta, al hacer scroll, que el stand-up meeting de un lunes de resaca.

Como bien apuntó Francho en un comentario, una manera de ganar velocidad haciendo scroll es omitiendo los reiterados findViewById cada vez que hay que cambiar los valores del famoso convertView. En su lugar, debemos hacer uso de los tag que podemos asignar a las filas (rows) de nuestro listado. ¿A quién no le suenan las funciones setTag() y getTag()? Pues hace tiempo, a nosotros ni se nos pasaban por la cabeza. Luego de ver el magnífico video de la Google I/O 2009, Making your Android UI Fast and Efficient, hicimos una capillita en la oficina a Romain Guy. Merece la pena cada año ver los videos de la Google I/O…

Muy bien. Con esto que he nombrado arriba, tenemos solucionado una parte de nuestro problema; es decir: que cada vez que se haga scroll y haya que buscar los widgets cuyas propiedades han de cambiar, no se haga siempre mediante el findViewById(), si no que utilicemos los tags.
La otra parte del problema, venía al experimentar que aún habiendo mejorado la velocidad de scroll un poquito, no conseguíamos ese efecto de fluidez que una lista ha de tener. ¿Por qué? Muy sencillo: cada nueva fila que aparecía en el listado al hacer scroll contenía uno, o a veces más, widgets cuyo contenido era calculado en el mismo momento del getView(). A veces eran cálculos de sumas, fáciles. Otras veces eran consultas a la Base de Datos, lo cual nos estaba robando mili-segundos de muy alto precio al hacer que el scroll de tu aplicación sea fluido.

Por lo tanto, otro mandamiento que nos grabamos a fuego en la mente: No harás consultas a la Base de Datos en el getView() de tus propios adapters. Las consultas, se hacen antes del setAdapter() del ListView. Si no, arderás en el infierno de los developers.

Y ya, para rizar el rizo de la optimización de un listado y hacerlo más rápido que Usain Bolt: Dime cuál es la fuente de datos que alimenta tu Adapter, y te diré cuántos mili-segundos perderás en cada getView().
¿Por qué digo esto? Por que, como bien dijo Pedro en un post: hay que evitar a toda costa el trabajar con ArrayList de objetos propios en android. Reflection lo llaman, ¿no? Android y reflection no son buenos amigos.
Así que en lugar de acceder a los datos que poblarán nuestros ListView de manera limpia y con código legible, como puede ser un ArrayList, os recomendamos que uséis directamente el objeto Cursor devuelto por la Base de Datos. Sí: mucho más sucio el código, pero no veas lo rápido que va todo.

En resumen, hoy hemos visto:

  1. El ViewHolder para evitar millones de findViewById()
  2. No hacer consultas a la Base de Datos dentro de un getView()
  3. La fuente de datos de un Adapter, a ser posible, que sea un Cursor

¿Me olvido algo?

Rework, de Jason Fried y David Heinemeier Hansson

Por más que lo pienso no puedo dejar de alabar este libro: Rework, de 37Signals, compañía de software y creadores de Ruby on Rails (entre otros productos).

El libro es tremendamente incisivo, en el aspecto de que no tiene problemas en ir a degüello con los problemas del día a día dentro de una empresa. Utiliza un lenguaje simple y muchas veces llega a adquirir un tono de complicidad con el lector.

Está estructurado en forma de capítulos cortos (poco más de una hoja) con títulos realmente impactantes. Para muestra un botón de algunos de ellos:

  • ASAP is poison. En una empresa, realmente todos querrían que algo se terminara cuanto antes posible. Además es inflacionario: cuanto más lo uses, menos valdrá.
  • Own your bad news.  Si algo sale mal, sé el primero en anunciarlo. Por lo menos no dependerás de la desinformación que puedan publicar otros medios.
  • They’re not thirteen. ¿Por qué poner permisos a los empleados para hacer cosas? Permíteles tomar decisiones, de lo contrario estarás poniéndoles una pegatina de “no me fío de ti”.
  • The best are everywhere. La geografía no importa. Contrata a los mejores estén donde estén. Aquí en Frogtek seguimos este patrón a rajatabla 🙂
  • Marketing is not a department. Si te encuentras en una empresa y tienes un producto final, entonces ¡toda tu empresa es marketing! Todos están trabajando para marketing a tiempo completo (o deberían).

Os recomiendo encarecidamente a todos aquellos que trabajéis en una empresa (en cualquier tipo de cargo). Además es muy fácil de leer y sin duda os hará pensar, que es el objetivo principal que debería tener un libro sobre empresas.

El findViewById(), y lo cargante que puede llegar a ser

Si no vienes del post introductorio, quizás te gustaría leerlo.

Segundo asalto. La aplicación sigue estando lenta, y no sabemos muy bien por qué. Ya no hay tantos onMeasure(); sólo los “necesarios”. Pero hay una nueva función que aparece en los primeros puestos de nuestro querido traceview: findViewTraversal(). ¿Y quién llama tantísimas veces a esta última? pues findViewById().
Y es que esta función, es la que enlaza un recurso de la interfaz de usuario de una aplicación, con una variable en nuestro código. O simplemente se encarga de acceder a ese recurso para cambiar una de sus propiedades.

Supongamos, pues, que tenemos un layout con una veintena de widgets entre layouts, Buttons, EditText, TextView, ImageView… Y que en un momento dado del ciclo de vida de la actividad, tenemos que acceder a uno ó varios de ellos para cambiar sus propiedades. Podemos acceder a ellos del modo:
((EditText) this.findViewById(R.id.edittext_cliente)).setOnClickListener(…);
Aunque también podemos guardar el valor que devuelve la función findViewById() en una variable, o variable miembro de la Activity para operar con ella varias veces a lo largo del citado ciclo de vida.

¿Cuál es la diferencia? y ya que estamos ¿cuál era nuestro error? Pues que cada vez que se llama a la función findViewById(), android recorre todo el árbol jerárquico del Layout que hemos definido previamente en XML, en busca del recurso al que queremos acceder. Como ya habréis averiguado, el costo de esa operación es muy alto si el control a encontrar es de los últimos en el árbol, o si hay muchos niveles de layouts anidados, o simplemente queremos cambiar el valor de una de las propiedades de ese recurso bastantes veces a lo largo del ciclo de vida de la aplicación.
Si a esto le sumamos que el cambio en una de las propiedades de un EditText (por ejemplo), afecta a cinco o seis widgets más; y a todos accedemos del modo findViewById(), estamos vendidos.

La solución para una Activity, cuyos widgets han de ser modificados con bastante asiduidad es el guardar el resultado de la función archinombrada en este post en variables miembro, a las cuales se accederá sin tener que recorrer (findViewTraversal()) todo el árbol jerárquico “n” veces, con la consiguiente pérdida de velocidad en la transición de un estado a otro de la Activity.
Eso sí: Podemos prescindir de estas variables miembro si accedemos una, y sólo una, vez al widget en cuestión.

Esto también hay que grabarlo a fuego en la mente.

Antiguas entradas Recientes entradas