2010-04-15 17 views
8

Mi carrera comenzó como un desarrollador de paradigmas funcionales (LISP), y ahora soy un desarrollador de .net/C#. Por supuesto que estoy enamorado de LINQ. Sin embargo, también creo en (1) usar la herramienta adecuada para el trabajo y (2) preservar el principio KISS: de los más de 60 ingenieros con los que trabajo, tal vez solo el 20% tiene horas de experiencia en el paradigma LINQ/funcional, y el 5% tener 6 a 12 meses de tal experiencia. En resumen, me siento obligado a mantenerme alejado de LINQ a menos que me impida alcanzar un objetivo sin él (en el que reemplazar 3 líneas de código O-O con una línea de LINQ no es un "objetivo").¿Cuándo se usa demasiado LINQ (a los objetos)?

Pero ahora uno de los ingenieros, con una experiencia LINQ/functional-paradigm de 12 meses, está utilizando LINQ para objetos, o al menos expresiones lambda de todos modos, en todas las ubicaciones imaginables del código de producción. Mis diversos llamamientos al principio de KISS no han dado ningún resultado. Por lo tanto ...

¿Qué estudios publicados puedo apelar próximamente? ¿Qué pauta de "estándares de codificación" ha inventado otros con cierto éxito? ¿Hay problemas de rendimiento publicados de LINQ que pueda señalar? En resumen, estoy tratando de lograr mi primer objetivo, KISS, por persuasión indirecta.

Por supuesto, este problema podría extenderse a un sinnúmero de otras áreas (como el uso excesivo de métodos de extensión). Tal vez hay una guía "uber", muy respetada (por ejemplo, estudios publicados, etc.) que tiene un efecto más amplio en esto. ¿Cualquier cosa?

ÚLTIMA EDICIÓN: ¡Guau! ¡Me educaron! Estoy de acuerdo en que estoy llegando a esto completamente equivocado. Pero como aclaración, por favor mire abajo el código de muestra que estoy viendo en realidad. Originalmente compilado y trabajado, pero su propósito ahora es irrelevante. Solo ve con la "sensación" de eso. Ahora que vuelvo a visitar esta muestra medio año después, estoy obteniendo una imagen muy diferente de lo que es en realidad molestándome. Pero me gustaría tener mejores ojos que los míos para hacer los comentarios.

//This looks like it was meant to become an extension method... 
public class ExtensionOfThreadPool 
{ 
    public static bool QueueUserWorkItem(Action callback) 
    { 
     return ThreadPool.QueueUserWorkItem((o) => callback()); 
    } 
} 

public class LoadBalancer 
{ 
    //other methods and state variables have been stripped... 

    void ThreadWorker() 
    { 
     // The following callbacks give us an easy way to control whether 
     // we add additional headers around outbound WCF calls. 
     Action<Action> WorkRunner = null; 

     // This callback adds headers to each WCF call it scopes 
     Action<Action> WorkRunnerAddHeaders = (Action action) => 
     { 
      // Add the header to all outbound requests. 
      HttpRequestMessageProperty httpRequestMessage = new HttpRequestMessageProperty(); 
      httpRequestMessage.Headers.Add("user-agent", "Endpoint Service"); 

      // Open an operation scope - any WCF calls in this scope will add the 
      // headers above. 
      using (OperationContextScope scope = new OperationContextScope(_edsProxy.InnerChannel)) 
      { 
       // Seed the agent id header 
       OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestMessage; 

       // Activate 
       action(); 
      } 
     }; 

     // This callback does not add any headers to each WCF call 
     Action<Action> WorkRunnerNoHeaders = (Action action) => 
     { 
      action(); 
     }; 

     // Assign the work runner we want based on the userWCFHeaders 
     // flag. 
     WorkRunner = _userWCFHeaders ? WorkRunnerAddHeaders : WorkRunnerNoHeaders; 

     // This outter try/catch exists simply to dispose of the client connection 
     try 
     { 
      Action Exercise =() => 
      { 
       // This worker thread polls a work list 
       Action Driver = null; 
       Driver =() => 
       { 
        LoadRunnerModel currentModel = null; 
        try 
        { 
         // random starting value, it matters little 
         int minSleepPeriod = 10; 
         int sleepPeriod = minSleepPeriod; 

         // Loop infinitely or until stop signals 
         while (!_workerStopSig) 
         { 
          // Sleep the minimum period of time to service the next element 
          Thread.Sleep(sleepPeriod); 

          // Grab a safe copy of the element list 
          LoadRunnerModel[] elements = null; 
          _pointModelsLock.Read(() => elements = _endpoints); 

          DateTime now = DateTime.Now; 
          var pointsReadyToSend = elements.Where 
           (
            point => point.InterlockedRead(() => point.Live && (point.GoLive <= now)) 
           ).ToArray(); 

          // Get a list of all the points that are not ready to send 
          var pointsNotReadyToSend = elements.Except(pointsReadyToSend).ToArray(); 

          // Walk each model - we touch each one inside a lock 
          // since there can be other threads operating on the model 
          // including timeouts and returning WCF calls. 
          pointsReadyToSend.ForEach 
          (
           model => 
           { 
            model.Write 
            (
             () => 
             { 
              // Keep a record of the current model in case 
              // it throws an exception while we're staging it 
              currentModel = model; 

              // Lower the live flag (if we crash calling 
              // BeginXXX the catch code will re-start us) 
              model.Live = false; 

              // Get the step for this model 
              ScenarioStep step = model.Scenario.Steps.Current; 

              // This helper enables the scenario watchdog if a 
              // scenario is just starting 
              Action StartScenario =() => 
              { 
               if (step.IsFirstStep && !model.Scenario.EnableWatchdog) 
               { 
                model.ScenarioStarted = now; 
                model.Scenario.EnableWatchdog = true; 
               } 
              }; 

              // make a connection (if needed) 
              if (step.UseHook && !model.HookAttached) 
              { 
               BeginReceiveEventWindow(model, step.HookMode == ScenarioStep.HookType.Polled); 
               step.RecordHistory("LoadRunner: Staged Harpoon"); 
               StartScenario(); 
              } 

              // Send/Receive (if needed) 
              if (step.ReadyToSend) 
              { 
               BeginSendLoop(model); 
               step.RecordHistory("LoadRunner: Staged SendLoop"); 
               StartScenario(); 
              } 

             } 
            ); 
           } 
           ,() => _workerStopSig 
          ); 

          // Sleep until the next point goes active. Figure out 
          // the shortest sleep period we have - that's how long 
          // we'll sleep. 
          if (pointsNotReadyToSend.Count() > 0) 
          { 
           var smallest = pointsNotReadyToSend.Min(ping => ping.GoLive); 
           sleepPeriod = (smallest > now) ? (int)(smallest - now).TotalMilliseconds : minSleepPeriod; 
           sleepPeriod = sleepPeriod < 0 ? minSleepPeriod : sleepPeriod; 
          } 
          else 
           sleepPeriod = minSleepPeriod; 
         } 
        } 
        catch (Exception eWorker) 
        { 
         // Don't recover if we're shutting down anyway 
         if (_workerStopSig) 
          return; 

         Action RebootDriver =() => 
         { 
          // Reset the point SendLoop that barfed 
          Stagepoint(true, currentModel); 

          // Re-boot this thread 
          ExtensionOfThreadPool.QueueUserWorkItem(Driver); 
         }; 

         // This means SendLoop barfed 
         if (eWorker is BeginSendLoopException) 
         { 
          Interlocked.Increment(ref _beginHookErrors); 
          currentModel.Write(() => currentModel.HookAttached = false); 
          RebootDriver(); 
         } 
         // This means BeginSendAndReceive barfed 
         else if (eWorker is BeginSendLoopException) 
         { 
          Interlocked.Increment(ref _beginSendLoopErrors); 
          RebootDriver(); 
         } 
         // The only kind of exceptions we expect are the 
         // BeginXXX type. If we made it here something else bad 
         // happened so allow the worker to die completely. 
         else 
          throw; 
        } 
       }; 

       // Start the driver thread. This thread will poll the point list 
       // and keep shoveling them out 
       ExtensionOfThreadPool.QueueUserWorkItem(Driver); 

       // Wait for the stop signal 
       _workerStop.WaitOne(); 

      }; 

      // Start 
      WorkRunner(Exercise); 
     } 
     catch(Exception ex){//not shown} 
    } 
} 
+0

Las únicas quejas que haré sobre ese fragmento no tienen nada que ver con su pregunta original. 1. Enfoque inconsistente para nombrar a los locales (deben ser 'camelCase', pero aquí algunos son' PascalCase'). 2. Captura la clase base universal 'Exception'. –

+0

@Daniel: ¿No te molestan los lambda que tienen 140 líneas de largo y contienen 3 o 4 niveles anidados de 20 a 40 líneas lambdas, para un método general que tiene 200 líneas de longitud? ¿Has visto esta publicación ?: http://stackoverflow.com/questions/2627662/anonymous-methods-lambdas-coding-standards –

+0

Hay dos problemas por separado: 1. ¿Las lambdas son algo bueno o malo (en sí mismas)? 2. ¿Cuándo un alcance de la función es demasiado largo? Estoy de acuerdo en que cuando una función se vuelve realmente larga y retorcida, puede beneficiarse de que se divida en funciones nombradas por separado solo para mayor claridad, por lo que está más claro qué variables son visibles a qué partes de código. Pero ese es un problema de funciones largas en general, no específicas de lambdas. (Cont ...) –

Respuesta

15

Bueno, me parece que usted es el que quiere complicar más el código, porque cree que sus colegas no están a la altura de un enfoque genuinamente simple.En muchos casos, muchos Encuentro LINQ a objetos hace que el código más simple - y sí que lo hace incluir el cambio de sólo unas pocas líneas a uno:

int count = 0; 
foreach (Foo f in GenerateFoos()) 
{ 
    count++; 
} 

convertirse

int count = GenerateFoos().Count(); 

por ejemplo.

los casos en que no es hacer el código más simple, está bien para tratar de alejarse de LINQ - pero lo anterior es un ejemplo en el que sin duda no está obstaculizado considerablemente evitando LINQ, pero el "beso" el código es claramente el código LINQ.

Parece que su empresa podría beneficiarse de entrenar a sus ingenieros para aprovechar LINQ to Objects, en lugar de tratar de atraer siempre al mínimo común denominador.

+1

Estoy de acuerdo. Los ingenieros necesitan sumergirse en LINQ y en la bondad lambda. para todo, por supuesto, pero nada bueno puede provenir de evitarlo para siempre. La compañía se quedará atrás, al igual que los empleados. –

+0

Estoy vendido, excepto que los métodos de extensión de BCL preexistentes (como Count() demostrado) no es el problema para mí En cambio, ¿qué piensas de una firma de registro que toma deliberadamente solo lambdas en lugar de cadenas: // supone ~ 10000 apariciones de este ejemplo en el código ... Logger.Log ((= = "Hola"); // o ... Logger.Log ((= = String.Format ("hey {0}", "there")); El objetivo es lograr un aumento del rendimiento (cuestionable) si el registro está desactivado. –

+3

@Mystagogue: En realidad, ayer estuve hablando con colegas sobre aplazar la evaluación de argumentos en las declaraciones de registro. Es una buena idea cuando no hay efectos secundarios, ya que significa que puede registrar de manera condicional los detalles potencialmente costosos para formatear, sabiendo que la evaluación no tendrá lugar a menos que el registro detallado esté habilitado. Prefiero eso a 'if (Logger.IsEnabled (info)) {Logger.Info (...); } 'que es la alternativa común. –

8

usted parece estar igualando LINQ a objetos con mayor complejidad, ya que se asume que el uso innecesario de Viola "mantenerlo simple, estúpido".

Toda mi experiencia ha sido la contraria: hace que los algoritmos complejos sean mucho más simples de escribir y leer.

Por el contrario, considero que la programación imperativa, basada en enunciados, mutación de estado como la opción "arriesgada" se utilizará solo cuando sea realmente necesario.

Así que le sugiero que se esfuerce para lograr que más colegas comprendan el beneficio. Es una falsa economía tratar de limitar sus enfoques a aquellos que usted (y otros) ya entienden, porque en esta industria rinde enormes dividendos para mantenerse en contacto con las "nuevas" prácticas (por supuesto, esto no es nuevo, pero como usted señala, es nuevo para muchos desde un fondo Java o C# 1.x).

En cuanto a tratar de fijar algunos cargos de "problemas de rendimiento" en él, no creo que vayas a tener mucha suerte. La sobrecarga implicada en Linq-to-objects en sí es minúscula.