20 de noviembre de 2017

Redirección fuertemente tipada en ASP.NET MVC

Recientemente volví a caer en un error de los que considero de primer año: cambié la firma de un método de acción en un controlador de MVC y olvidé buscar el 100% de todos los lugares que redirigían hacia él. Por supuesto, esto causó un error --que afortunadamente fue encontrado en QA-- ya que el código en cuestión (claro está) no tenía pruebas de ninguna clase.

El código en cuestión era similar al siguiente:

public ActionResult Foo()
{
  ...
  return RedirectToAction(
      "SomeAction", "SomePlace",
      new { usr = "Username", key = blah });
}

El problema es que fue necesario agregar un parámetro al método SomePlaceController.SomeAction, pero absolutamente nada en el código me advirtió que había nada más que cambiar además de la función de JavaScript que estaba modificando en ese momento. Fue la gota que derramó el vaso: desde que he estado dando mantenimiento a código legacy en ASP.NET MVC, no sé cuantas veces me ha sucedido este mismo problema y ya era hora de hacer algo al respecto, así que decidí que ya que estoy trabajando con C#, tal vez podría utilizar su sistema de tipos a mi favor. Lo que me gustaría poder hacer sería algo así:

public ActionResult Foo()
{
  ...
  return RedirectToAction<SomePlaceController>(
    x => x.SomeAction("Username", blah, 125));
}

Por supuesto, no existe una definición de RedirectToAction como esta, pero es relativamente fácil de definir usando expresiones de linq:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;

using RouteValue = System.Tuple<string, object>;

public static class RedirectTo
{
  #region Public Methods

  private const string InvalidTarget =
    "El argumento debe ser una invocación a un método del controlador.";

  private const string UnsupportedExpression = "Expresion no soportada";

  #endregion

  #region Public Methods

  public static RedirectToRouteResult RedirectToAction<T>(
    this T controller, Expression<Action<T>> selector)
    where T : Controller => Action(selector);

  public static RedirectToRouteResult Action<T>(
    Expression<Action<T>> selector) where T : Controller
  {
    if (!(selector.Body is MethodCallExpression))
    {
      throw new ArgumentException(InvalidTarget, nameof(selector));
    }

    var methodEx = (MethodCallExpression)selector.Body;

    var routeValues = MakeRouteValues(
      controllerName: GetControllerName<T>(),
      actionName: GetActionName(methodEx),
      arguments: GetArguments(methodEx));

    return new RedirectToRouteResult(routeValues);
  }

  #endregion

  #region Methods

  private static string GetControllerName<T>() where T : Controller
  {
    const string Suffix = "Controller";

    var name = typeof(T).Name;

    return name.EndsWith(Suffix)
      ? name.Substring(0, name.Length - Suffix.Length)
      : name;
  }

  private static string GetActionName(MethodCallExpression expression)
    => expression.Method.Name;

  private static RouteValue[] GetArguments(MethodCallExpression expression)
  {
    var names = expression.Method.GetParameters().Select(o => o.Name);
    var values = expression.Arguments.Select(GetValueOf);

    return names
      .Zip(values, (n, v) => new RouteValue(n, v))
      .Where(o => o.Item2 != null)
      .ToArray();
  }

  private static object GetValueOf(Expression expression)
  {
    if (expression is MemberExpression)
    {
      return GetValueOf((MemberExpression)expression);
    }
    else if (expression is ConstantExpression)
    {
      return GetValueOf((ConstantExpression)expression);
    }

    throw new InvalidOperationException(UnsupportedExpression);
  }

  private static object GetValueOf(MemberExpression expression)
  {
    var container = GetValueOf(expression.Expression);

    if (expression.Member is FieldInfo)
    {
      return ((FieldInfo)expression.Member).GetValue(container);
    }
    else if (expression.Member is PropertyInfo)
    {
      return ((PropertyInfo)expression.Member).GetValue(container);
    }

    throw new InvalidOperationException(UnsupportedExpression);
  }

  private static object GetValueOf(ConstantExpression expression)
    => expression.Value;

  private static RouteValueDictionary MakeRouteValues(
    string actionName, string controllerName, params RouteValue[] arguments)
  {
    var args = new RouteValue[]
      {
        new RouteValue("action", actionName),
        new RouteValue("controller", controllerName),
      };

    return new RouteValueDictionary(args
      .Concat(arguments)
      .ToDictionary(o => o.Item1, o => o.Item2));
  }

  #endregion
}

Con esto, el sintaxis que obtenemos es muy similar al que queríamos:
public ActionResult Foo()
{
  ...
  return RedirectTo.Action<SomePlaceController>(
    x => x.SomeAction("Username", blah, 125));
}

o incluso
public ActionResult SomeAction(
  string username, Guid key, long token)
{
  var credentials = GetCredentials(username, token);
  ...
  return this.RedirectToAction(x => x.Continue(username, credentials));
}

Con esto, me aseguraré de que la próxima vez que necesite agregar un parámetro al un método de un controlador, no pueda hacerlo sin tener por lo menos que revisar cualquier redirección que haya hacia el mismo (por lo menos en código compilado del lado del servidor).

11 de septiembre de 2017

"agile" está en el ojo de quien mira

Recientemente cambié de trabajo –se pueden dar cuenta por que tengo tiempo de escribir esto– y mi nuevo hogar es una empresa que ha decidido abrazar agile como su forma de hacer las cosas. Así, “agile” con “a” minúscula, como me lo dijera Sergio Acosta la primera vez que platicamos, refiriéndose a un agile sin marcas registradas, sin pedigrí.

En un principio pensé que tendría yo algo que enseñar al equipo al cual me estaba integrando. Si bien, hemos tenido algunas pláticas por demás interesantes sobre algunas cuestiones de diseño, TDD, etc., de primera instancia este último par de meses han sido una incesante carrera por entender el negocio. No digo que no hay nada que mejorar –siempre hay algo– si no que en el punto donde se encuentra el equipo actualmente, son muy buenos en lo que hacen:
  • Han integrado elementos a su proceso de desarrollo de aquí y de allá como apoyo al quehacer del día a día, sin seguir necesariamente una metodología fija.
  • Cuando sucede un incidente que afecta al equipo, al negocio o a la capacidad del equipo de entregar valor, se dedica el tiempo necesario para entenderlo y tomar medidas para que no vuelva a pasar (o por lo menos que no nos vuelva a tomar desprevenidos).
  • Tienen los problemas habituales cualquier equipo trabajando con código legacy, especialmente cuando se trata de desarrollar funcionalidad nueva sin caer en los mismos antipatrones anteriores… y a veces no lo logramos.
  • Mientras que algunos jamás escriben una sola prueba unitaria (que en ocasiones resulta prácticamente imposible por las características del código), otros intentan hacer pruebas por lo menos para cada nueva característica y algunos incluso intentan hacer pruebas para el código legacy antes de intentar cambiarlo.
    Pero nuevamente, a pesar de las limitaciones, existe una ventaja fundamental: el equipo sabe en dónde está la mayor parte de la deuda técnica y aprovecha cualquier oportunidad para reducirla.
  • Técnicamente es un equipo sólido, pero no hay rock stars. Y creo seriamente que la forma de trabajo del mismo de hecho previene el surgimiento de dichos personajes. Simplemente no son necesarios.

Pero quizás la característica más notable del equipo es su capacidad de comunicación (por supuesto, incluyendo a “producto”, pues todos somos un solo equipo). Algunos mientras que miembros del equipo se integran en sesiones impromptu de pair programming, otros trabajan la mayor parte del tiempo de esta forma, e incluso otros casi siempre trabajan solos. Pero sin importar cual sea su configuración favorita para trabajar, la mayor parte del tiempo todo el equipo sabe lo que están haciendo los demás, por qué e inclusive cómo. Nada pasa desapercibido. Ni una falla, ni un éxito. Todos están prestos a ofrecer ayuda cuando se necesita.

Me atrevo a opinar que esta es la característica que hace de este equipo un equipo efectivo, cohesivo y valioso para el negocio. Y es sin duda, el aspecto que debo aprender y dominar antes siquiera de hablar siquiera por primera vez de asuntos técnicos.

Así que no, lo que hacemos no es Scrum, ni Kanban, ni FDD, ni mucho menos XP. Solo hacemos agile y nada más.