jueves, 26 de noviembre de 2015

Contratación ¿En qué me estoy equivocando?

Aunque no acostumbro a escribir posts de opinión (básicamente porque creo que mi opinión no aporta mucho en casi ningún tema), hoy haré una merecida excepción porque quiero compartir con el resto una situación que me está sorprendiendo y frustrando a partes iguales y además no parece tener visos de solucionarse en un futuro cercano.

Después de 10 años como empresario/autónomo/auto-empleado, todo el equipo anterior (la friolera de 3) hemos emprendido juntos una nueva aventura en una especie de start-up (la llamo así porque no sé cómo se llama ahora a una empresa de reciente creación, pero bueno, no tenemos futbolín, aunque sí partimos de una situación más o menos estable en la que ya tenemos clientes y un producto en el mercado, así que entiendo que tampoco damos el perfil startapil al 100%).

En cualquier caso, una de las premisas iniciales del proyecto es ampliar el equipo técnico con una nueva incorporación. ¡Un nuevo fichaje! Estoy ilusionado, pasar de 3 a 4 es mucho, para algunos no significará nada, pero para mí supone colocar la cuarta pata a una mesa que espero aguante mucho tiempo.

Las primeras preguntas que tuvimos resolver fueron qué perfil buscar y que sueldo ofrecer.

En cuando al perfil (y después de mucho divagar), apostamos por un full-stack centrado en tecnologías .NET. Es decir, un back-end (ASP.NET MVC, Web API, Entity Framework), pero que se defienda razonablemente bien con Javascript y jQuery. La verdad es que perfil es un clon de nosotros mismos. Esto puede ser bueno o malo según como se mire, pero por ahora apostamos por el músculo y en ahondar en lo que ya sabemos (por ahora los experimentos con gaseosa).

En relación al sueldo, hemos determinado que podemos llegar hasta 33K… incluso negociables, fíjate tú. ¡Esto no puede fallar!, es un buen sueldo, ya sé que no es mega-sueldo pero tampoco es un mierdi-sueldo, yo creo (y si no es verdad me corriges) que para España no está mal.

Además, tenemos un buen horario (que por ahora más o menos cumplimos), de lunes a jueves de 8 a 17:00 y los viernes de 8 a 14:00. A mí me gusta mucho esa frase de “un ritmo de trabajo sostenible y sostenido”, es decir, si el éxito de la nueva empresa depende de que nos quedemos echando horas por sistema, claramente algo está mal.

Y con estos mimbres nos lanzamos al mercado laboral seguros de encontrar a un nuevo compañero…

Y a partir de aquí empieza nuestro vía crucis.

Lo que en principio parecería iba a ser una tarea sencilla se está convirtiendo en todo un dolor de muelas. Podemos argumentar que yo no soy de RRHH, luego probablemente no manejo ciertas variables en una entrevista que seguro debería, pero a grandes rasgos intento ser cordial, respetuoso y crear un ambiente favorable para una conversación distendida (en el fondo incluso me siento culpable por tener que decidir si alguien es apto o no para un trabajo, pero ese es otro tema, es más rollo personal…).

Para la preselección de candidatos hemos optado por una empresa de recruiting (que para ser justos están esforzándose mucho por cumplir las expectativas), luego la gente que finalmente entrevistamos ya ha pasado un filtro previo en el que nos aseguran que la persona tiene una salud mental estable e incluso sabe de programación (al menos su CV así lo dice y en una entrevista con un no-técnico parten la pana). Por otro lado, yo me he preparado las típicas preguntas sacadas del típico post ¿Qué preguntar cuando entrevistas a un candidato? Por ejemplo, ¿Te gusta lo que haces? ¿Te formas? ¿Cuál es el mayor reto al que te has enfrentado? ¿Te gusta trabajar en equipo? (todavía nadie ha contestado que no, pero tiempo al tiempo…) Vamos, que me siento ridículo en esta parte de la entrevista y en ese momento tanto entrevistado como entrevistador seguro lo estamos pasando muy mal.

Como al principio invertimos mucho tiempo en ver gente que tenía importantes suficientes carencias técnicas llegamos a la conclusión de que el filtro previo de la empresa de recruiting tenía que incorporar una pequeña prueba técnica (no para que ellos la evalúen, sino para que nos manden los resultados y así tengamos algo más de contexto antes de decidir si ver o no a la persona).

Esa primera entrevista técnica es la siguiente:

  • POO
    • Diferencias entre una clase abstracta y una interfaz
  • ASP.NET MVC
    • Diferencia entre @Html.Partial y @Html.Action
    • Diferencia entre @helper y @functions
  • ASP.NET WebAPI
    • ¿Para qué sirve [HttpGet] y [HttpPost]?
    • ¿Para qué sirve [FromUri] y [FromBody]?
  • Entity Framework
    • Diferencia entre Database First y Code First
    • ¿Qué hace DbSet.Find?
    • Diferencia entre IEnumerable e IQueryable
    • Lazy loading vs Eager loading vs Explicit loading
  • Javascript/jQuery
    • Hablando de eventos ¿Qué es la delegación?
    • ¿Qué es un closure?
    • ¿Qué es una promise?

Por descontado que no pretendo que nadie me dé una respuesta académica a cada una de las preguntas, pero sí por lo menos que en sus propias palabras conteste algo relacionado con el tema y que tenga cierto sentido.

También cabe decir que inicialmente (en mis mundos de arco iris y unicornios) había preparado un Word con más de 70 preguntas en distintos niveles o fases. Claro ¿Cómo iba a elegir de entre los buenos al mejor sino haciendo un poco de pair-programming con él e intercalando hábilmente preguntas de todo tipo?

Bueno, pues si vierais las respuestas lo fliparíais. Sólo 2 personas de 8 han contestado razonablemente bien. “Razonablemente” es una palabra que uso mucho ahora como medida de protección contra mi creciente frustración.

Ya después de esta selección hay una entrevista (que si puedo siempre hago por Skype). En esta entrevista intento (con más o menos éxito) hacer preguntas técnicas sin que parezca un incómodo interrogatorio. Voy a poner un ejemplo de cómo creo yo debería discurrir la entrevista:

“¿Qué opinas de los genéricos en C#? Y el tema de las lambas, qué movida ¿no? ¿Y cómo te llevas con las promises en Javascript? Oye, seguro que me puedes enseñar algo de código, ¿no? Mira, te voy a enseñar yo este otro código ¿Qué te parece? Y qué importante es el tema de lazy loading vs eager loading vs explicit loading en Entity Framework, ¿verdad? ¿Y los filtros en MVC?”

Y ya si la conversación va viento en popa le pregunto:

“¡Qué locura this en Javascript! ¿verdad? ¡Uff! Como ha cambiado el tema de los ValueProviders y ModelBinders entre ASP.NET MVC y WebAPI, ¡estos de Microsoft nos quieren matar!”

Pues te puedes imaginar, esta entrevista es imaginaria y no ha sucedido… y con franqueza no sé si llegará a suceder alguna vez. Siendo así, la pregunta que me hago es ¿En qué me estoy equivocando?

Si yo fuera mañana a hacer una entrevista de empleo, lo primero que haría sería colgar código en github o similar (si no recuerdo mal me van a querer contratar por mi código – además de por otras softskills, pero no es el tema del post de hoy). También me leería (y por qué no me estudiaría) los típicos posts de “Top interview questions about [inserta aquí tecnología/lenguaje]”.

Es cierto que eso no funcionará porque la entrevista no es un examen, no vale con la memoria a corto plazo, esto va de bagaje personal y de cómo encaras y has encarado la profesión… pero, en cualquier caso, optar a un trabajo de programador.NET en el año 2015 y no saber defender con una mínima coherencia las diferencias entre una interface y una clase abstracta me parece un pecado capital (IMHO). Igualmente, no me digas que haces front-end si no sabes (ni te suena) lo que es promise o closure.

En este punto queda claro que algo está fallando. ¿Posibles reflexiones? Yo hago las siguientes.

¿Tendré una visión distorsionada del mercado laboral y por 33K tengo que contratar a un padawan en vez de a un señor programador? Puede ser, yo creo que no, que es un sueldo majo, pero ya dudo de todo…

¿La gente buena ya está pillada (y cobrando un sueldo igual o superior al ofrecido) y además cambiar la comodidad y seguridad de una antigüedad no les hará atractivo el proyecto?

Yo asumo que quien venga tendrá un periodo de adaptación y no será parte productiva real del equipo hasta que no pase un tiempo. Todos necesitamos un periodo de adaptación, conocer la empresa, sus miserias y virtudes, descubrir el dominio del negocio en el que se está trabajando e incluso aprender a contar hasta tres cuando te toque lidiar con código legacy, porque sí, amigo, de ese código “viejuno” hay mucho, también hay código “fresquito”, nuevo y rimbombante, pero del otro hay más y no podemos renunciar a ello, pero lo que sí tengo claro es que si dices que sabes Code First y no te suena el método OnModelCreating… mal vamos… venga, vale, que usas DataAnnotations, aceptamos barco…

¿Realmente el nivel “general” (nótese el entrecomillado) en España es el que yo creía que era? De nuevo aquí podría ser que tuviera una percepción equivocada respecto a este tema y en esto tiene mucha culpa Twitter, los blogs, ¡la comunidad! Reconozco que lo último es echar la culpa a la comunidad, ¡pobre ella! J pero es que yo quiero en mi equipo gente que quiera salir de su zona de confort, perdón, rectifico, gente que YA haya salido de su zona de confort, y eso está siendo muy difícil, y eso sólo lo veo en la gente de Twitter, en la gente que participa en cualquiera de los bolos de MsCoders, Madrid.NET, Formación Tajamar o similar (y participar es “ir”, “oír”, no significa ser el ponente).

Tampoco pretendo con este post encontrar respuestas a mis problemas (que seguramente serán los problemas de muchos) pero al menos sí espero poder ver a gente conocida y en vez de contarles mi película, que ya la hayan visto y me den su más sincera opinión, yo la agradeceré.

Actualización de hoy mismo: No dejes de leer los comentarios, de veras, son todos muy oportunos y valen más que el propio post. Si tuviera que volver a escribirlo probablemente ya sería otro. El objetivo principal del post (que no era otro sino recabar opiniones y aprendar con ello) se ha cumplido y con creces. He cambiado de opinión en algunos puntos y eso es bueno, no me cuesta reconocerlo, cambio mucho de opinión. Y por cierto, aquí está el enlace de la oferta https://betabeers.com/post/programador-fullstack-net-2379/

Un saludo!

martes, 24 de noviembre de 2015

Crear índices en EF con Fluent API

Crear un índice vía Fluent API ha resultado ser una tarea nada intuitiva y más difícil de lo esperado. Es por ello que lo voy a dejar por escrito y así, siguientes veces, no tendré que navegar por mi código para buscar la chuleta de cómo hacerlo, porque en mi opinión es un código irrecordable y que no nunca logro descubrir a golpe de Intellisense.

Partiendo de una entidad muy sencilla como la siguiente:

class Customer

{

    public int Id { get; set; }

    public string Name { get; set; }

    public string Country { get; set; }

    public DateTime CreatedDate { get; set; }

}

Hacer un índice único por el campo Name sería asi:

modelBuilder

    .Entity<Customer>()

    .Property(t => t.Name)

    .HasMaxLength(250)

    .HasColumnAnnotation(IndexAnnotation.AnnotationName,

        new IndexAnnotation(new IndexAttribute() { IsUnique = true }));

Si esto ya parece un código algo enrevesado, crear un índice por varios campos es aun mas sorpredente porque es imprescindible dar un nombre al índice y una posición a cada campo:

var customer = modelBuilder.Entity<Customer>();

const string indexName = "IX_Customers";

customer

    .Property(t => t.Name)

    .HasMaxLength(250)

    .HasColumnAnnotation(

        IndexAnnotation.AnnotationName,

        new IndexAnnotation(

            new IndexAttribute(indexName, 1)

            {

                IsUnique = true

            }));

customer

    .Property(t => t.Country)

    .HasMaxLength(250)

    .HasColumnAnnotation(

        IndexAnnotation.AnnotationName,

        new IndexAnnotation(

            new IndexAttribute(indexName, 2)

            {

                IsUnique = true

            }));

El poner un tamaño a las propiedades de tipo string es porque si no se mapearán a nvarchar(max) y la creación del índice fallará en Sql Server.

Originalmente el post llevaba hasta aquí, pero el bueno de @gulnor me incito me incitó de mala manera a dar una solución y no sólo exponer un problema, y claro, después de leer su reciente y reflexivo post Código abierto, ecosistema cerrado, me he sentido un poco culpable y no he tenido otra que corresponder con una posible solución para que que así no se diga que no contribuyo (o al menos lo intento) :)

La idea es poder crear un índice sin todo el ruido que genera el código anterior, algo asi:

modelBuilder.Entity<Customer>().Index(p => p.Name, true, "IX_Customers", 1);

modelBuilder.Entity<Customer>().Index(p => p.Country, true, "IX_Customers", 2);

modelBuilder.Entity<Customer>().Index(p => p.CreatedDate);

El código con los métodos extensores es el siguiente (en el que no me gusta el nombre del parámetro genérico T2 y además reconozco abusar de la indentación, pero es por un buen motivo, para facilitar la lectura en el blog):

static class ModelBuilderExtensions

{

    private static void CreateIndex(

        PrimitivePropertyConfiguration property,

        bool isUnique = false,

        string name = null,

        int position = 1)

    {

        var indexAttribute = new IndexAttribute();

        if (!string.IsNullOrWhiteSpace(name))

        {

            indexAttribute = new IndexAttribute(

                name,

                position <= 0 ? 1 : position);

        }

        indexAttribute.IsUnique = isUnique;

        property.HasColumnAnnotation(

            IndexAnnotation.AnnotationName,

            new IndexAnnotation(indexAttribute));

    }

 

    public static void Index<T, T2>(

        this EntityTypeConfiguration<T> entity,

        Expression<Func<T, T2>> propertyExpression,

        bool isUnique = false,

        string name = null,

        int position = 1) where T : class where T2 : struct

    {

        var property = entity.Property(propertyExpression);

        CreateIndex(property, isUnique, name, position);

    }

 

    public static void Index<T, T2>(

        this EntityTypeConfiguration<T> entity,

        Expression<Func<T, T2?>> propertyExpression,

        bool isUnique = false,

        string name = null,

        int position = 1) where T : class where T2 : struct

    {

        var property = entity.Property(propertyExpression);

        CreateIndex(property, isUnique, name, position);

    }

 

    public static void Index<T>(

        this EntityTypeConfiguration<T> entity,

        Expression<Func<T, string>> propertyExpression,

        bool isUnique = false,

        string name = null,

        int position = 1) where T : class

    {

        var property = entity.Property(propertyExpression);

        CreateIndex(property, isUnique, name, position);

    }

}

Ahora sí, post cerrado!

Un saludo!