2012/01/28

[offtopic] Contrato de grupo de trabajo práctico

Tras unas malas experiencias grupales, cuya parte de responsabilidad asumo, me encontré con que una vez suavizadas mi intolerancia, belicosidad, soberbia, falta de paciencia (y la lista sigue, pero esto no es acerca de mi), la vida grupal seguía siendo difícil. Así que pensé una manera de obligar al resto a cumplir con lo que yo estaba dispuesto a cumplir, léase, buscar almas afines, a las cuales no obligar.

El problema es que cuando uno manda un mail diciendo "busco grupo", si uno pone "comprometido y proactivo" todos los parias como yo que se han quedado boyando mágicamente serán "comprometidos y proactivos".

El primer paso es ser selectivo en el lugar de convocatoria. Es mejor en el trabajo que en una lista de la facultad. Si todo sale mal, no es lo mismo cruzarse ocasionalmente en un pasillo a todos los días en el comedor. Esto fuerza un poco a todas las partes a que la cosa salga un poco mejor.

La facultad/materia/cátedra/período era FIUBA/Organización de Datos/Saubidet/2011/1, conocida por su dificultad.

Aquí pego el contrato tal como fue enviado. En caso de querer utilizar, es open source, pero mejor leer la discusión posterior para hacer los ajustes y controles necesarios.



Estimados/as:

Busco entre 0 y 2 personas para formar un grupo con las siguientes características:

A) Indispensables:

A.1) Comenzamos a trabajar en el tp ANTES de tener el enunciado, preparando el entorno de desarrollo, el repositorio, las convenciones a utilizar, identificando conocimientos y limitaciones de cada integrante.

A.2) Que dedique al menos dos horas diarias al tp, aparte del resto de la materia.

A.3) Que esta sea su única o principal materia y esté dispuesta a sacrificar muy pero muy temprano las otras en su beneficio.

A.4) NO corremos a último momento, NO nos quedamos sin dormir para entregar. El tp debe estar listo una semana antes del plazo establecido.

A.5) Versionamiento con svn.

A.6) Integración continua.

A.7) C++ linux.

B) Negociables:

B.1) Uso intensivo de valgrind.

B.2) Entorno de programación con un Makefile, no ide (a menos que respete ese Makefile).

B.3) Que haya cursado taller con Veiga hasta el final (aunque no haya aprobado).

B.4) Que trabaje, asi estamos en igualdad de sufrimiento.

B.5) Entre 0 y 3 personas.

C) ¿Qué ofrezco?

C.1) Respetar todos los puntos.

C.2) Ser responsable de integración aparte de mi parte.

C.3) El Makefile y su mantenimiento.

C.4) Garantizo entorno de desarrollo unificado brindando guía a cada integrante

D) Además estoy dispuesto a utilizar, en el marco de A.1:

D.1) Servidor de integración continua con Continuum o similar

D.2) Documentación javadoc con doxygen.

D.2) Unit testing con cppunit.


Justificaciones:

A.1 a A.4: Es una materia muy difícil, la cátedra no explica muchos temas indispensables, no dedica horas de clase a explicaciones del tp y hay temas que se dan a último momento.

A.5, A.6, C.2 y D.1: Para no tener sorpresas de último momento.

A.7: Desde C++ se puede hacer exactamente lo mismo que desde C y ya tengo varias clases preparadas.

B.1, D.2 y D.3: Hace a la calidad.

B.2 y C.3: Hago el trabajo desde múltiples máquinas, algunas de ellas incapaces de correr IDEs complejas como eclipse.

B.3 y B.4: Para que estemos más parejos


¿Cómo hago ahora para que esto no sea tan offtopic? Fácil, cuento como me fué.

El enunciado lo recibimos casi inmediatamente, asi que no hubo necesidad de "ganar tiempo". (Tomar nota de esto). Como se predica con el ejemplo, comenzé a programar no bien resolvimos un mínimo diseño, usando TDD con cppunit.

Mis compañeros, con sus nombres de fantasía, no sé, Pablo y Luís, seis semanas más tarde se incorporaron. Asi que un dia me encontre con todo mi código con su impecable cppunit y una carpeta al lado, que era una copia reducida de lo mío, con recias modificaciones y sin rastros de la carpeta de tests.

Esto se transformó en una breve y brutal escaramuza:

Pablo: ¿Cómo me decís que no aceptás mi código si funciona y ni lo miraste?

Charly: Si lo miré, pero no entendí los cambios, no soy tan brillante, apenas entiendo lo que yo hice.

Pablo: Es una falta de respeto de tu parte.

Charly: No están respetando el contrato. Si metés todos esos cambios y tirás los test yo no tengo por que mirar nada.

Pablo: Sí, estoy respetando el contrato, vos dijiste que proveías los tests.

¡ALTO AHI!

Acá es donde nos falló el contrato. El muchacho interpretó que yo, Charly, iba a hacer el unit testing. Releyéndolo ahora veo que podría ser así interpretado, si no fuera por que TODOS sabemos que el unit testing es del programador. ¿O no? Puede ser que otra persona ponga algunos tests, pero a medida que se avanza en el problema, aparecen nuevos que sólo el programador puede destilar.

No hace falta agregar que el muchacho no usó o modificó los tests ya que él consideraba que el test va al final.


Supongo que en realidad este problema se hubiera mitigado si hubiésemos cumplido con A.1, donde habría saltado el problema.

A.1) Comenzamos a trabajar en el tp ANTES de tener el enunciado (OK), preparando el entorno de desarrollo (OK), el repositorio (OK), las convenciones a utilizar (OK), identificando conocimientos (FAIL) y limitaciones de cada integrante (FAIL). 


Para esto habia que tomar nota. Quizas, si hubiera habido más tiempo antes de recibir el enunciado, podríamos haber detectado y solucionado mejor esto.

El resultado de la experiencia fue que aprobamos sin llorar ni robar, incluso gracias a Luis nos ganamos un punto extra para el examen de promoción (o similar) gracias a que en factor de compresión arrasó.

Mi conclusión: quise llevar al estudio lo que considero buenas prácticas profesionales, fracasando miserablemente. El principal riesgo para la aprobación del trabajo práctico fue eso, no el trabajo en sí.


Resultados:

  • No puedo contar el haber aprobado como resultado de esta acción. Lo voy a formular asi: "Haber aprobado PESE a esta acción".
  • El tp de la siguiente materia, Sistemas Operativos, lo hicimos juntos. Mi parte la hice con TDD, sin entrar en conflicto.
  • Luis, unos meses despues me dijo: "Charly, tengo que aprender TDD"

2012/01/04

TDSL segunda semana

El comienzo del viaje fue plácido pero con nubarrones amenazantes en el horizonte. Como el problema lo veia un tanto complicado para testear con mis conocimientos funcionales, decidí descomponer el problema más allá de lo habitual.

En pocas palabras, lo que hay que hacer es dada una palabra, generar todas las combinaciones según un mapa, los detalles están en http://seguridad-agile.blogspot.com/2011/12/tdsl.html

En php el código es [1], que emite por STDOUT en lugar de acumular, asi que la implementación es un poco menos sencilla de lo que se ve. Ya que lo voy a hacer bien, prescindiré de STDOUT, elevando el nivel de dificultad.

Parte del algoritmo que he implementado consiste en tranformar la palabra provista en un lista con sus posibles combinaciones, por ejemplo "hola" se transforma en:

[ ["h", "H"], ["o","O","0"],["l","L","1"],["a","A","4","@"] ]

El test es

preleet_nothing_test()->
  ?assertEqual([],transform:preleet("")).

preleet_one_simple_char_test()->
  ?assertEqual([[";"]],transform:preleet(";")).

preleet_one_complex_char_test()->
  ?assertEqual([["b","B","8"]],transform:preleet("b")).

preleet_many_mixed_char_test()->
  ?assertEqual([["a","A","4","@"],[";"],["b","B","8"]],transform:preleet("a;b")).


Mi implementación de esto es [2], pero la pongo al final por si querés probarlo.

Sin tener aún el aplomo de encarar lo más duro y a modo de entrenamiento, me desvié con una función que luego me resultará útil para profiling. Lo que hace es dada la lista anterior, calcular la cantidad de combinaciones.

El test es este y la implementación en [3]:

measure_empty_test()->
  ?assertEqual(0,transform:measure([])).

measure_one_element_size_one_test()->
  ?assertEqual(1,transform:measure([["1"]])).

measure_one_element_size_two_test()->
  ?assertEqual(2,transform:measure([["b","B"]])).

measure_two_elements_size_one_test()->
  ?assertEqual(1,transform:measure([["b"],["B"]])).

measure_two_elements_size_two_test()->
  ?assertEqual(4,transform:measure([["b","B"], ["a","A"]])).

measure_arbitrary4_test()->
  ?assertEqual(4,transform:measure([["b","B"], ["c"],["a","A"]])).

measure_arbitrary8_test()->
  ?assertEqual(8,transform:measure([["b","B"], ["c"],["a","A","4","@"]])).


Debo confersar que he pecado. Estuve en un vertiginoso remolino de codificación incompleta, tests que se fueron ajustando al código y viceversa sin ningún resultado. De repentete, a las 4:30 me llevé por delante la solución, que es tan groseramente sencilla, una vez que la conocés, claro, que no sé si ponerla. El problema es que estaba trabajando con partes internas de la implementación usando tdd cuando encontré la solución completa. Ops! tengo el resultado antes que el test, pero juro que no fue a propósito. Asi que este es el test y en [4] la implementación.

leet_a_test() ->
 ?assertEqual(["a","A","4","@"],transform:leet("a")).
leet_aa_test() ->
 ?assertEqual(["aa","aA","a4","a@","Aa","AA","A4","A@","4a","4A","44","4@","@a","@A","@4","@@"],transform:leet("aa")).
leet_bc_test() ->
 ?assertEqual(["bc","bC","b(","Bc","BC","B(","8c","8C","8("],transform:leet("bc")).


Lo único que falta es que dado a->A,@,4, tener A->a,@,4 y etc. pero me parece que lo voy a hardcodear en map/1.


[1] Implementación original en php. No entiendo bien lo que hice ni me interesa mucho. Me parece que el primer foreach agrega mayúsculas y "relaciones inversas", o sea si existe "a"-> "A"  crea o agrega "A"->"a". El segundo foreach genera el array de permutaciones (sea lo que sea) necesario para el string de entrada. Noto una cierta optimización.

$map = array(
    'a'=> array('@','4'),
    'e'=> array('3'),
   etc...
);

$baseArray = str_split($argv[1],1);

foreach ($baseArray as $char) {
    if (!isset($map[$char])) {
        $map[$char][]=$char;
    }

    if (! in_array(strtoupper($char) ,$map[$char] )) {
        $map[$char][]=strtoupper($char);
    }

    if (! in_array($char ,$map[$char] )) {
        $map[$char][]=$char;
    }
}

foreach ($baseArray as $pos=>$char) {
    $permArray[$pos]=$map[$char];
}

emit($permArray,0,'');

function emit(array $permArray, $level, $buffer) {
    if ($level >= count($permArray)) {
        echo "$buffer\n";
        return;
    }
    foreach ($permArray[$level] as $char) {
        emit($permArray, $level + 1, "$buffer$char");
    }
}


[2]  implementación de preleet/1

preleet([]) -> [];
preleet([Head|Tail]) -> [ find([Head], map()) | preleet(Tail) ].


siendo map/1

map() -> [
    {"a",["a","A","4","@"]},
    {"b",["b","B","8"]} ,
    {"c",["c","C","("]},


y el test de find/2

find_a_in_empty_map_test()->
  ?assertEqual(["a"],transform:find("a",[])).

find_a_in_map_first_test()->
  Map = [ { "a", [ "a", "A", "4", "@"] }, {"b", ["b","B"]} , {"c",["c","C","("]}],
  ?assertEqual([ "a", "A", "4", "@"],transform:find("a",Map)).

find_a_in_map_last_test()->
  Map = [ {"b", ["b","B"]} , {"c",["c","C","("]}, { "a", [ "a", "A", "4", "@"] } ],
  ?assertEqual([" "],transform:find(" ",Map)).

find_a_in_map_with_only_a_test()->
  Map = [ { "a", [ "a", "A", "4", "@"] }],
  ?assertEqual(["a","A", "4", "@"],transform:find("a", Map)).

find_something_in_map_without_something_test()->
  Map = [ {"b", ["b","B"]} , {"c",["c","C","("]}],
  ?assertEqual([";"],transform:find(";",Map)).

[3] implementación de measure/1

measure([]) -> 0;
measure(What) -> measure(What,1).
measure([],Total) -> Total;
measure([Load|Tail],Total) -> measure(Tail, length(Load) * Total).


[4] implementación de leet/1

leet(Word)->
  lists:foldl(
    fun (Elem, List) ->
    [ string:concat(X,E) || X <- List, E <-Elem ] end,
    hd(preleet(Word)), tl(preleet(Word) )).

2012/01/03

Intermedio

Afortunadamente he podido ligar el aprendizaje de erlang con las dos partes actuales de mi actividad profesional. Además de seguridad debo encargarme a mi pesar de administrar jira, algo totalmente no gratificante para mi. Quizas a otras personas les guste, pero yo lo detesto desde los más profundo de mi ser.

Ese rechazo se ha plasmado en el deseo de automatizar; dejémosle a las máquinas el trabajo sucio. ¿Dije antes "seguridad"? ¿Dije "jira"? ¿Dije "erlang"? ¡Buenísimo!


El otro día me enteré que jira ofrece una api [1] y me dije: Si pudiera mediante una api consultar las relaciones entre usuarios, grupos, roles y proyectos, tendría la posibilidad de detectar cualquier desviación de la configuración, como un cliente que haya sido asignado como administrador de un proyecto.


En esta ocasión, dejé el tdd de lado, es una vergüenza, pero es lo que hay.


La idea es poder consultar los grupos de usuarios y los usuarios con sus roles en cada proyecto y luego cruzarlos para detectar irregularidades


El proceso es muy sencillo:
  1. Generar un json según la llamada a efectuar [2]
  2. Llamar a jira
  3. Parsear el resultado
  4. Hacer los cruces
Primero hay que hacer login() para obtener un token, que es lo que se usa en las llamadas posteriores como autenticación.

La librería de erlang que use no soporta proxy con https, lo cual me dificultó al comienzo diagnosticar, pues necesitaba ver el tráfico y como jira usa https estaba afuera. Como logré establecer la comunicación exitosamente, no fue vital el problema. Con wireshark se pudo haber hecho teniendo la clave privada del servidor, lo cual no es posible en este caso.

Las llamadas a getGroup(), getProjectsNoSchemes() o getProjectWithSchemesById() son triviales y funcionan sin sorpresas, una vez que se ha podido parsear la respuesta.

En general es asi:

Json= make_json_getGroup({"Developers",Token}),

Url = "https://host.com/rpc/json-rpc/jirasoapservice-v2",

{ok, {{HttpVer, Code, Msg}, Headers, Body}} = 
http:request(post, { Url, [], "application/json", Json },  [], []),

{[{<<"id">>,_}|[{<<"result">>,{[{<<"name">>,_}| [ { <<"users">>, Users}]]}}|[{<<"jsonrpc">>,_}]]]}=
json_eep:json_to_term(Body),

En este caso, nos quedan los usuarios. Si nos interesara, nos podriamos quedar con el id, name y jsonrpc.

Esos dibujitos <<>> alrededor de los strings significan que en realidad son datos binarios y no debería confersarlo, pero me frenó casi una hora.

El problema fue cuando intenté obtener más datos de roles/usuarios por proyecto. Las firmas[3] de los métodos anteriores son:

RemoteGroup getGroup(java.lang.String token, java.lang.String groupName)
RemoteProject getProjectByKey(java.lang.String token, java.lang.String projectKey)
RemoteProjectRole getProjectRole(java.lang.String token, java.lang.Long id)

Pero la firma siguiente  es:

RemoteProjectRoleActors getProjectRoleActors(
   java.lang.String token,
   RemoteProjectRole projectRole,
   RemoteProject project 
)


Intenté ver via netbeans, que tiene un plugin para jira, el tráfico a ver si había algo que me ayudara, pero no hace las llamadas que necesitaba. Para esto use webscarab y le dije a netbeans que lo usara como proxy. Lo interesante es que netbeans no dijo nada, pese a recibir los certificados incorrectos del proxy en lugar de jira.com. Ya que estaba, subí el bug a netbeans[4].

Entonces, ¿cómo le paso un "RemoteProject"? Consideré hacer el conocido ataque de fuerza bruta de probar con key, "key", id, "id" para proyecto y rol pero tras dos combinaciones me aburrí, pensé en Murphy y me dije: "seguro que la solución correcta es la última" y fui directo a ella:

  Project = get_project_by_id({{token,Token},{project_id,"1234"}}),
 
  [ProjectRole|Roles] = get_project_roles({token,Token}),

  get_project_role_actors(

   {{token,Token},{project_role,ProjectRole},{project,Project}}
  ).

Más que sorpresa alivio me produjo que funcionara de una. No entiendo a quien se le ocurrió implementarlo asi. Alguien me preguntó si habia probado de sacarle algunos campos, ya que estoy enviando Project y ProyectRole:

{[{<<"key">>,<<"XXXX">>},
   {<<"description">>,null},
   {<<"url">>,<<"https://host.com/browse/XXXX">>},
   {<<"lead">>,<<"xxxxx.xxxxxxxx">>},
   {<<"permissionScheme">>,null},
   {<<"notificationScheme">>,null},
   {<<"issueSecurityScheme">>,null},
   {<<"projectUrl">>,null},
   {<<"name">>,<<"Xxxxx">>},
   {<<"id">>,<<"111111">>}]}. 

y

 {[{<<"name">>,<<"Administrators">>},
   {<<"id">>,111111},
   {<<"description">>,
    <<"A project role that represents administrators in a project">>}]}}.


y le contesté lo que supongo que es el motivo por el cual está hecho asi: "¿funciona? ¡LISTO!".

[1] https://developer.atlassian.com/display/JIRADEV/JIRA+JSON-RPC+Overview

[2] https://github.com/jchris/erlang-json-eep-parser

[3] http://docs.atlassian.com/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html

[4] http://netbeans.org/bugzilla/show_bug.cgi?id=206848