¿Los bloques más aumentan la complejidad del código? [cerrado]

18

Aquí hay un ejemplo muy simplificado . Esta no es necesariamente una pregunta específica del idioma, y le pido que ignore las muchas otras formas en que se puede escribir la función y los cambios que se le pueden hacer. . El color es de un tipo único.

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Muchas personas que he conocido, ReSharper, y este tipo (cuyo comentario me recordó que he estado buscando preguntar esto por un tiempo) recomendarían refactorizar el código para eliminar el elsebloqueo dejando esto:

(No recuerdo lo que la mayoría ha dicho, de lo contrario no habría preguntado esto)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Pregunta: ¿Hay un aumento en la complejidad introducido al no incluir el elsebloque?

Tengo la impresión de que la elseintención indica más directamente, al afirmar el hecho de que el código en ambos bloques está directamente relacionado.

Además, encuentro que puedo evitar errores sutiles en la lógica, especialmente después de modificaciones en el código en una fecha posterior.

Tome esta variación de mi ejemplo simplificado (ignorando el hecho del oroperador, ya que este es un ejemplo simplificado a propósito):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Alguien ahora puede agregar un nuevo ifbloque basado en una condición después del primer ejemplo sin reconocer inmediatamente de manera correcta que la primera condición impone una restricción a su propia condición.

Si elsehubiera un bloque presente, quien agregara la nueva condición se vería obligado a mover el contenido del elsebloque (y si de alguna manera lo pasan por alto, la heurística mostrará que el código es inalcanzable, lo que no ocurre en el caso de que uno iflimite a otro) .

Por supuesto, hay otras formas en que el ejemplo específico debe definirse de todos modos, todas las cuales evitan esa situación, pero es solo un ejemplo.

La longitud del ejemplo que di puede sesgar el aspecto visual de esto, así que suponga que el espacio ocupado entre paréntesis es relativamente insignificante para el resto del método.

Olvidé mencionar un caso en el que estoy de acuerdo con la omisión de un bloque else, y es cuando uso un ifbloque para aplicar una restricción que debe cumplirse lógicamente para todos los códigos siguientes, como una verificación nula (o cualquier otra protección) .

Selali Adobor
fuente
En realidad, cuando hay una declaración "si" con un "retorno", puede verse como "bloqueo" en> 50% de todos los casos. Así que creo que su idea errónea es que un "bloqueo" es el caso excepcional, y su ejemplo el caso normal.
Doc Brown
2
@AssortedTrailmix: estoy de acuerdo en que un caso como el anterior puede ser más legible al escribir la elsecláusula (a mi gusto, será aún más legible al omitir los corchetes innecesarios. Pero creo que el ejemplo no está bien elegido, porque en el en el caso anterior, escribiría return sky.Color == Color.Blue(sin if / else). Un ejemplo con un tipo de retorno diferente a bool probablemente aclararía esto.
Doc Brown,
1
Como se señaló en los comentarios anteriores, el ejemplo sugerido es demasiado simplificado e invita a respuestas especulativas. En cuanto a la parte de la pregunta que comienza con "Alguien ahora puede agregar un nuevo bloque if ..." , se ha preguntado y respondido muchas veces antes, ver por ejemplo ¿ Enfoques para verificar múltiples condiciones? y múltiples preguntas vinculadas a él.
mosquito
1
No veo qué más tienen que ver los bloques con el principio DRY. DRY tiene más que ver con la abstracción y las referencias al código de uso común en forma de funciones, métodos y objetos de lo que está pidiendo. Su pregunta está relacionada con la legibilidad del código y posiblemente con las convenciones o modismos.
Shashank Gupta
2
Votación para reabrir. Las buenas respuestas a esta pregunta no están particularmente basadas en la opinión. Si el ejemplo dado en una pregunta es inadecuado, mejorarlo mediante la edición sería lo razonable, no votar para cerrar por una razón que no es realmente cierta.
Michael Shaw

Respuestas:

27

En mi opinión, el bloque else explícito es preferible. Cuando veo esto:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

Sé que estoy lidiando con opciones mutuamente excluyentes. No necesito leer qué hay dentro de los bloques if para poder contar.

Cuando veo esto:

if (sky.Color != Blue) {
   ...
} 
return false;

Parece, a primera vista, que devuelve falso independientemente y tiene un efecto secundario opcional que el cielo no es azul.

Tal como lo veo, la estructura del segundo código no refleja lo que realmente hace el código. Eso es malo. En su lugar, elija la primera opción que refleje la estructura lógica. No quiero tener que verificar la lógica de retorno / interrupción / continuación en cada bloque para conocer el flujo lógico.

Por qué alguien preferiría el otro es un misterio para mí, espero que alguien tome la posición opuesta y me ilumine con su respuesta.

Olvidé mencionar un caso en el que estoy de acuerdo con la omisión de un bloque else, y es cuando uso un bloque if para aplicar una restricción que debe cumplirse lógicamente para todos los códigos siguientes, como una verificación nula (o cualquier otra protección )

Diría que las condiciones de guardia están bien en el caso de que hayas dejado el camino feliz. Se produjo un error o falla que impide que la ejecución ordinaria continúe. Cuando esto sucede, debe lanzar una excepción, devolver un código de error o lo que sea apropiado para su idioma.

Poco después de la versión original de esta respuesta, se descubrió un código como este en mi proyecto:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

Al no poner el retorno dentro de los demás, se pasó por alto el hecho de que faltaba una declaración de retorno. Si se hubiera empleado otra cosa, el código ni siquiera se habría compilado. (Por supuesto, la preocupación mucho mayor que tengo es que las pruebas escritas en este código fueron bastante malas para no detectar este problema).

Winston Ewert
fuente
Eso resume mis pensamientos al respecto, me alegro de no ser el único que piensa así. Trabajo con programadores, herramientas e IDE prefiriendo la eliminación del elsebloque (puede ser molesto cuando me veo obligado a hacerlo solo para mantenerme en línea con las convenciones para una base de código). Yo también estoy interesado en ver el contrapunto aquí.
Selali Adobor
1
Yo varío en esto. Algunas veces lo explícito no le está ganando mucho, ya que es poco probable que active los errores lógicos sutiles que menciona el OP: es solo ruido. Pero sobre todo estoy de acuerdo, y ocasionalmente haré algo como } // elselo que normalmente iría el otro como un compromiso entre los dos.
Telastyn
3
@Telastyn, pero ¿qué obtienes al saltarte el resto? Estoy de acuerdo en que el beneficio es muy leve en muchas situaciones, pero incluso por el ligero beneficio, creo que vale la pena hacerlo. Ciertamente, me parece extraño poner algo en un comentario cuando podría ser código.
Winston Ewert
2
Creo que tiene el mismo error que el OP: "si" con "retorno" puede verse principalmente como un bloqueo de guardia, por lo que está discutiendo aquí el caso excepcional, mientras que el caso más frecuente de bloqueos de guardia es en mi humilde opinión más legible sin el "más".
Doc Brown
... así que lo que escribió no está mal, pero en mi humilde opinión, no vale la pena los muchos votos positivos que recibió.
Doc Brown
23

La razón principal para eliminar el elsebloqueo que he encontrado es el sangrado excesivo. El cortocircuito de los elsebloques permite una estructura de código mucho más plana.

Muchas ifdeclaraciones son esencialmente guardias. Están evitando la desreferenciación de punteros nulos y otros "¡no hagas esto!" errores Y conducen a una terminación rápida de la función actual. Por ejemplo:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Ahora puede parecer que una elsecláusula sería muy razonable aquí:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

Y, de hecho, si eso es todo lo que está haciendo, no es mucho más sangrado que el ejemplo anterior. Pero, esto rara vez es todo lo que está haciendo con estructuras de datos reales de varios niveles (una condición que ocurre todo el tiempo cuando se procesan formatos comunes como JSON, HTML y XML). Entonces, si desea modificar el texto de todos los elementos secundarios de un nodo HTML dado, diga:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Los guardias no aumentan la sangría del código. Con una elsecláusula, todo el trabajo real comienza a moverse hacia la derecha:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Esto está comenzando a tener un movimiento significativo hacia la derecha, y este es un ejemplo bastante trivial, con solo dos condiciones de guardia. Cada guardia adicional agrega otro nivel de sangría. El código real que he escrito procesando XML o consumiendo feeds JSON detallados puede acumular fácilmente 3, 4 o más condiciones de protección. Si siempre usa el else, termina con sangría de 4, 5 o 6 niveles. Quizás más. Y eso definitivamente contribuye a una sensación de complejidad del código, y más tiempo dedicado a comprender qué se alinea con qué. El estilo rápido, plano y de salida temprana elimina parte de esa "estructura en exceso", y parece más simple.

Apéndice Los comentarios sobre esta respuesta me hicieron darme cuenta de que podría no haber sido claro que el manejo NULL / empty no es la única razón para los condicionales de guardia. ¡No cerca! Si bien este ejemplo simple se enfoca en el manejo NULL / empty y no contiene otras razones, la búsqueda de estructuras profundas como XML y AST para contenido "relevante", por ejemplo, a menudo tiene una larga serie de pruebas específicas para eliminar nodos irrelevantes y sin interés casos. Una serie de pruebas de "si este nodo no es relevante, devolver" provocará el mismo tipo de desplazamiento hacia la derecha que los guardias NULL / empty. Y en la práctica, las pruebas de relevancia posteriores se combinan con frecuencia con guardias NULL / vacío para las estructuras de datos correspondientes más profundas buscadas, un doble golpe para la deriva hacia la derecha.

Jonathan Eunice
fuente
2
Esta es una respuesta detallada y válida, pero voy a agregar esto a la pregunta (se me escapó al principio); Hay una "excepción" en la que siempre encuentro un elsebloque superfluo, cuando uso un ifbloque para aplicar una restricción que debe cumplirse lógicamente para todos los códigos siguientes, como una verificación nula (o cualquier otra protección).
Selali Adobor
@AssortedTrailmix Creo que si excluye los casos en los que rutinariamente puede salir temprano de la thencláusula (como las sucesivas declaraciones de guardia), entonces no hay un valor particular para evitar la elsecláusula en particular . De hecho, reduciría la claridad y podría ser peligroso (al no indicar simplemente la estructura alternativa que está / debería estar allí).
Jonathan Eunice
Creo que es razonable usar condiciones de guardia para expulsar una vez que haya dejado el camino feliz. Sin embargo, también encuentro para el caso del procesamiento de XML / JSON que si primero valida la entrada contra un esquema, obtendrá mejores mensajes de error y un código más limpio.
Winston Ewert
Esta es una explicación perfecta de los casos en los que se evita más.
Chris Schiffhauer
2
Hubiera envuelto la API para no producir NULL tontos en su lugar.
Winston Ewert
4

Cuando veo esto:

if(something){
    return lamp;
}
return table;

Veo un idioma común . Un patrón común , que en mi cabeza se traduce en:

if(something){
    return lamp;
}
else return table;

Debido a que este es un idioma muy común en la programación, el programador que está acostumbrado a ver este tipo de código tiene una 'traducción' implícita en su cabeza cada vez que lo ve, que lo 'convierte' al significado adecuado.

No necesito lo explícito else, mi cabeza 'lo pone allí' porque reconoció el patrón en su conjunto . Entiendo el significado de todo el patrón común, por lo que no necesito pequeños detalles para aclararlo.

Por ejemplo, lea el siguiente texto:

ingrese la descripción de la imagen aquí

¿Leíste esto?

Amo a Paris en primavera

Si es así, estás equivocado. Lee el texto otra vez. Lo que en realidad está escrito es

Me encanta París en el la primavera

Hay dos "las" palabras en el texto. Entonces, ¿por qué te perdiste uno de los dos?

Es porque su cerebro vio un patrón común y lo tradujo inmediatamente a su significado conocido. No fue necesario leer todo detalle por detalle para descubrir el significado, porque ya reconoció el patrón al verlo. Es por eso que ignoró el extra "the ".

Lo mismo con el idioma anterior. Los programadores que han leído mucho código están acostumbrados a ver este patrón , por ejemploif(something) {return x;} return y; . No necesitan un explícito elsepara entender su significado, porque su cerebro "lo pone allí para ellos". Lo reconocen como un patrón con un significado conocido: "lo que está después de la llave de cierre se ejecutará solo como implícito elsedel anterior if".

Entonces, en mi opinión, elsees innecesario. Pero solo usa lo que parece más legible.

Aviv Cohn
fuente
2

Añaden desorden visual en el código, por lo que sí, podrían agregar complejidad.

Sin embargo, lo contrario también es cierto, la refactorización excesiva para reducir la longitud del código también puede agregar complejidad (no en este caso simple, por supuesto).

Estoy de acuerdo con la afirmación de que el elsebloque indica la intención. Pero mi conclusión es diferente, porque estás agregando complejidad en tu código para lograr esto.

No estoy de acuerdo con su afirmación de que le permite evitar errores sutiles en la lógica.

Veamos un ejemplo, ahora solo debería volver verdadero si el cielo es azul y hay alta humedad, la humedad no es alta o si el cielo es rojo. Pero luego, confundes un aparato ortopédico y lo colocas mal else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Hemos visto todo este tipo de errores tontos en el código de producción y puede ser muy difícil de detectar.

Sugeriría lo siguiente:

Esto me resulta más fácil de leer, porque puedo ver de un vistazo que el valor predeterminado es false.

Además, la idea de obligar a mover el contenido de un bloque para insertar una nueva cláusula es propensa a errores. Esto de alguna manera se relaciona con el principio abierto / cerrado (el código debe estar abierto para la extensión pero cerrado para la modificación). Estás obligando a modificar el código existente (por ejemplo, el codificador podría introducir un error en el equilibrio de llaves).

Finalmente, estás guiando a las personas a responder de una manera predeterminada que crees que es la correcta. Por ejemplo, presenta un ejemplo muy simple pero luego declara que las personas no deben tomarlo en consideración. Sin embargo, la simplicidad del ejemplo afecta toda la pregunta:

  • Si el caso es tan simple como el presentado, entonces mi respuesta sería omitir cualquier if else cláusula.

    return (sky.Color == Color.Blue);

  • Si el caso es un poco más complicado, con varias cláusulas, respondería:

    bool CanLeaveWithoutUmbrella () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • Si el caso es complicado y no todas las cláusulas devuelven verdadero (tal vez devuelven un doble), y quiero introducir algún punto de interrupción o comando de inicio de sesión, consideraría algo como:

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Si no estamos hablando de un método que devuelve algo, sino un bloque if-else que establece alguna variable o llama a un método, entonces sí, incluiría una elsecláusula final .

Por lo tanto, la simplicidad del ejemplo también es un factor a considerar.

FranMowinckel
fuente
Siento como si estuviera cometiendo el mismo error que otros han hecho y morphing el ejemplo un poco demasiado, por lo tanto el examen de un nuevo tema, pero tengo que tomar un segundo y examinar el proceso de llegar a su ejemplo, así que por ahora se ve Valido para mi. Y una nota al margen, esto no está relacionado con el principio abierto / cerrado . El alcance es demasiado estrecho para aplicar este modismo. Pero es interesante observar que su aplicación en un nivel superior estaría eliminando todo este aspecto del problema (eliminando la necesidad de cualquier modificación de la función).
Selali Adobor
1
Pero si no podemos modificar este ejemplo demasiado simplificado agregando cláusulas, ni modificar cómo está escrito, ni hacer ningún cambio, entonces debe considerar que esta pregunta se relaciona solo con esas líneas de código precisas y ya no es una pregunta general. En ese caso, tiene toda la razón, no agrega ninguna complejidad (ni la elimina) la cláusula else porque no había complejidad para empezar. Sin embargo, no estás siendo justo, porque eres el que sugiere la idea de que podrían agregarse nuevas cláusulas.
FranMowinckel
Y como digo al margen, no estoy afirmando que esto se relacione con el conocido principio abierto / cerrado. Creo que la idea de llevar a las personas a modificar su código como sugiere es propensa a errores. Solo estoy tomando esta idea de ese principio.
FranMowinckel
Espera las ediciones, estás haciendo algo bueno, estoy escribiendo un comentario ahora mismo
Selali Adobor
1

Aunque el caso if-else descrito tiene la mayor complejidad, en la mayoría (pero no en todas) las situaciones prácticas, no importa mucho.

Hace años, cuando estaba trabajando en un software que se utilizaba para la industria de la aviación (tenía que pasar por un proceso de certificación), surgió una pregunta suya. Resultó que había un costo financiero en esa declaración de "otra cosa", ya que aumentaba la complejidad del código.

Este es el por qué.

La presencia del 'else' creó un 3er caso implícito que tuvo que ser evaluado, sin tomar el 'if' ni el 'else'. Usted podría estar pensando (como yo estaba en ese momento) WTF?

La explicación fue así ...

Desde un punto de vista lógico, solo hay dos opciones: 'if' y 'else'. Sin embargo, lo que estábamos certificando era el archivo objeto que el compilador estaba generando. Por lo tanto, cuando había un 'else', se tenía que hacer un análisis para confirmar que el código de ramificación generado era correcto y que no aparecía un misterioso tercer camino.

Compare esto con el caso donde solo hay un 'si' y no 'otro'. En ese caso, solo hay dos opciones: la ruta 'if' y la ruta 'non-if'. Dos casos para evaluar son menos complejos que tres.

Espero que esto ayude.

Chispeante
fuente
en un archivo de objeto, ¿ninguno de los casos se representaría como jmps idénticos?
Winston Ewert
@ WinstonEwert - Uno esperaría que fueran lo mismo. Sin embargo, lo que se representa y lo que se espera que se represente son dos cosas diferentes, independientemente de cuánto se superpongan.
Sparky
(Supongo que SE se comió mi comentario ...) Esta es una respuesta muy interesante. Si bien diría que el caso que expone es un tanto nicho, muestra que algunas herramientas encontrarían una diferencia entre los dos (incluso la métrica de código más común basada en el flujo que veo, la complejidad ciclomática, no mediría ninguna diferencia.
Selali Adobor
1

El objetivo más importante del código es ser comprensible como comprometido (en lugar de refactorizarse fácilmente, lo cual es útil pero menos importante). Se puede usar un ejemplo de Python un poco más complejo para ilustrar esto:

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

Esto es bastante claro: es el equivalente de una casedeclaración o búsqueda de diccionario, la versión de nivel superior de return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

Esto es más claro, porque aunque el número de tokens es mayor (32 frente a 24 para el código original con dos pares clave / valor), la semántica de la función ahora es explícita: es solo una búsqueda.

(Esto tiene consecuencias para la refactorización: si desea tener un efecto secundario si key == NIKtiene tres opciones:

  1. Vuelva al estilo if/ elif/ elsee inserte una sola línea en elkey == NIK caso.
  2. Guarde el resultado de la búsqueda en un valor y agregue una ifdeclaración avalue para el caso especial antes de regresar.
  3. Ponga una ifdeclaración en la persona que llama.

Ahora vemos por qué la función de búsqueda es una simplificación poderosa: hace que la tercera opción sea obviamente la solución más simple, ya que además de dar como resultado una diferencia menor que la primera opción , es la única que se asegura de que valuesolo haga una cosa.)

Volviendo al código de OP, creo que esto puede servir como guía para la complejidad de las elsedeclaraciones: el código con una elsedeclaración explícita es objetivamente más complejo, pero más importante es si hace que la semántica del código sea clara y simple. Y eso tiene que ser respondido caso por caso, ya que cada parte del código tiene un significado diferente.

l0b0
fuente
Me gusta su reescritura de la tabla de búsqueda de la caja del interruptor simple. Y sé que es un idioma favorito de Python. En la práctica, sin embargo, a menudo encuentro que los condicionales de múltiples vías (también conocidos casecomo switchdeclaraciones) son menos homogéneos. Varias opciones son solo retornos de valor directo, pero uno o dos casos necesitan un procesamiento adicional. Y cuando descubra que la estrategia de búsqueda no es adecuada, debe volver a escribir en una if elif... elsesintaxis completamente diferente .
Jonathan Eunice
Es por eso que creo que la búsqueda generalmente debe ser de una sola línea o una función, de modo que la lógica en torno al resultado de la búsqueda se separe de la lógica de buscarlo en primer lugar.
l0b0
Esta pregunta aporta una visión de nivel superior a la pregunta que definitivamente debería mencionarse y tenerse en cuenta, pero creo que se ha puesto una restricción suficiente en el caso para que pueda sentirse libre de expandirse en su propia postura
Selali Adobor
1

Creo que depende de qué tipo de ifdeclaración estés usando. Algunosif declaraciones pueden verse como expresiones, y otras solo pueden verse como declaraciones de flujo de control.

Tu ejemplo me parece una expresión. En lenguajes tipo C, el operador ternario?: es muy parecido a una ifdeclaración, pero es una expresión, y la parte else no puede omitirse. Tiene sentido que una rama else no se pueda omitir en una expresión, porque la expresión siempre debe tener un valor.

Usando el operador ternario, su ejemplo se vería así:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Sin embargo, dado que es una expresión booleana, realmente debería simplificarse completamente para

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

La inclusión de una cláusula else explícita aclara su intención. Los guardias pueden tener sentido en algunas situaciones, pero al seleccionar entre dos valores posibles, ninguno de los cuales es excepcional o inusual, no ayudan.

Michael Shaw
fuente
+1, el OP es en mi humilde opinión discutir un caso patológico que debería escribirse mejor de la manera que mostró anteriormente. El caso mucho más frecuente de "si" con "retorno" es, según mi experiencia, el caso de "guardia".
Doc Brown
No sé por qué esto sigue sucediendo, pero la gente sigue ignorando el primer párrafo de la pregunta. De hecho, todos están usando la línea exacta de código que digo explícitamente que no se use. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
Y releyendo su última pregunta, parece que no entiende la intención de la pregunta.
Selali Adobor
Usaste un ejemplo que prácticamente rogaba que se simplificara. Leí y presté atención a su primer párrafo, razón por la cual mi primer bloque de código está allí, en lugar de saltar directamente a la mejor manera de hacer ese ejemplo en particular. Si crees que yo (o cualquier otra persona) he entendido mal la intención de tu pregunta, tal vez deberías editarla para aclarar tu intención.
Michael Shaw
Lo editaría, pero no sabría cómo dejarlo más claro, ya que uso la línea exacta de código que haces y digo, "no hagas esto". Hay infinitas maneras que usted podría reescribir el código y eliminar la pregunta, no puedo cubrir todos ellos, sin embargo, muchas personas han entendido mi declaración y respetado ...
Selali Adobor
1

Otra cosa a tener en cuenta en este argumento son los hábitos que promueve cada enfoque.

  • if / else reformula una muestra de codificación en términos de ramas. Como dices, a veces esta explicidad es buena. Realmente hay dos posibles rutas de ejecución y queremos resaltar eso.

  • En otros casos, solo hay una ruta de ejecución y luego todas esas cosas excepcionales de las que los programadores deben preocuparse. Como mencionó, las cláusulas de guardia son excelentes para esto.

  • Existe, por supuesto, el caso 3 o más (n), donde generalmente alentamos a abandonar la cadena if / else y usar una declaración case / switch o polimorfismo.

El principio rector que tengo en cuenta aquí es que un método o función debe tener un solo propósito. Cualquier código con una declaración if / else o switch es solo para bifurcación. Por lo tanto, debe ser absurdamente corto (4 líneas) y solo delegar en el método correcto o producir el resultado obvio (como su ejemplo). Cuando su código es tan corto, es difícil perder esos retornos múltiples :) Al igual que "goto considerado dañino", se puede abusar de cualquier declaración de ramificación, por lo que generalmente vale la pena pensar un poco en las declaraciones de lo contrario. Como mencionó, solo tener un bloque else proporciona un lugar para que el código se agrupe. Usted y su equipo tendrán que decidir cómo se siente al respecto.

De lo que realmente se queja la herramienta es del hecho de que tiene un retorno / descanso / lanzamiento dentro del bloque if. Entonces, en lo que a él respecta, escribiste una cláusula de guardia pero la arruinaste. Puede elegir hacer feliz la herramienta o puede "entretener" su sugerencia sin aceptarla.

Steve Jackson
fuente
Nunca pensé en el hecho de que la herramienta podría estar considerando la posibilidad de ser una cláusula de protección tan pronto como se ajuste al patrón para uno, ese es probablemente el caso, y explica el razonamiento para un "grupo de proponentes" por su ausencia
Selali Adobor
@AssortedTrailmix Correcto, estás pensando elsesemánticamente, las herramientas de análisis de código generalmente buscan instrucciones extrañas porque a menudo resaltan un malentendido del flujo de control. El elseafter an ifque regresa es bits desperdiciados. if(1 == 1)a menudo provoca el mismo tipo de advertencia.
Steve Jackson
1

Creo que la respuesta es que depende. Su código de muestra (como señala indirectamente) simplemente devuelve la evaluación de una condición, y se representa mejor como obviamente sabe, como una sola expresión.

Para determinar si agregar una condición else aclara u oculta el código, debe determinar qué representa IF / ELSE. ¿Es una (como en su ejemplo) una expresión? Si es así, esa expresión debe extraerse y usarse. ¿Es una condición de guardia? Si es así, entonces ELSE es innecesario y engañoso, ya que hace que las dos ramas parezcan equivalentes. ¿Es un ejemplo de polimorfismo procesal? Extraelo. ¿Está estableciendo condiciones previas? Entonces puede ser esencial.

Antes de decidir incluir o eliminar el ELSE, decida qué representa, eso le dirá si debe estar presente o no.

jmoreno
fuente
0

La respuesta es un poco subjetiva. Un elsebloque puede ayudar a la legibilidad al establecer que un bloque de código se ejecuta exclusivamente en otro bloque de código, sin necesidad de leer ambos bloques. Esto puede ser útil si, por ejemplo, ambos bloques son relativamente largos. Hay casos, aunque tener ifs sin elses puede ser más legible. Compare el siguiente código con varios if/elsebloques:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

con:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

El segundo bloque de código cambia las ifdeclaraciones anidadas en cláusulas de protección. Esto reduce la sangría y aclara algunas de las condiciones de retorno ("¡si a != b, entonces volvemos 0!")


Se ha señalado en los comentarios que puede haber algunos agujeros en mi ejemplo, por lo que voy a profundizar un poco más.

Podríamos haber escrito el primer bloque de código aquí para hacerlo más legible:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Este código es equivalente a los dos bloques anteriores, pero presenta algunos desafíos. La elsecláusula aquí conecta estas declaraciones if juntas. En la versión de la cláusula de protección anterior, las cláusulas de protección son independientes entre sí. Agregar uno nuevo significa simplemente escribir uno nuevo ifentre el último ify el resto de la lógica. Aquí, eso también se puede hacer, con solo una pequeña cantidad más de carga cognitiva. Sin embargo, la diferencia es cuando se necesita una lógica adicional antes de esa nueva cláusula de protección. Cuando las declaraciones fueron independientes, pudimos hacer:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

Con el conectado if/else cadena , esto no es tan fácil de hacer. De hecho, el camino más fácil sería romper la cadena para parecerse más a la versión de la cláusula de guardia.

Pero realmente, esto tiene sentido. Usar if/elsedébilmente implica una relación entre las partes. Podríamos suponer que a != bestá relacionado de alguna manera c > den la if/elseversión encadenada. Esa suposición sería engañosa, como podemos ver en la versión de la cláusula de guardia que, de hecho, no están relacionados. Eso significa que podemos hacer más con cláusulas independientes, como reorganizarlas (tal vez para optimizar el rendimiento de la consulta o para mejorar el flujo lógico). También podemos ignorarlos cognitivamente una vez que los hayamos pasado. En una if/elsecadena, estoy menos seguro de que la relación de equivalencia entre ayb no volverá a aparecer más tarde.

La relación implicada por elsetambién es evidente en el código de ejemplo profundamente anidado, pero de una manera diferente. Las elsedeclaraciones están separadas del condicional al que están unidas, y están relacionadas en orden inverso a las condicionales (la primera elseestá unida a la última if, la última elseestá unida a la primera if). Esto crea una disonancia cognitiva en la que tenemos que retroceder a través del código, tratando de alinear la sangría en nuestros cerebros. Es un poco como tratar de combinar un par de paréntesis. Se pone aún peor si la sangría está mal. Los frenos opcionales tampoco ayudan.

Olvidé mencionar un caso en el que estoy de acuerdo con la omisión de un bloque else, y es cuando uso un bloque if para aplicar una restricción que debe cumplirse lógicamente para todos los códigos siguientes, como una verificación nula (o cualquier otra protección )

Esta es una buena concesión, pero realmente no invalida ninguna respuesta. Podría decir eso en su ejemplo de código:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Una interpretación válida para escribir código como este podría ser que "solo debemos irnos con un paraguas si el cielo no es azul". Aquí hay una restricción de que el cielo debe ser azul para que el código siguiente sea válido para ejecutarse. He conocido la definición de tu concesión.

¿Qué pasa si digo que si el color del cielo es negro, es una noche clara, por lo que no es necesario un paraguas? (Hay formas más simples de escribir esto, pero hay formas más simples de escribir todo el ejemplo).

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

Como en el ejemplo más grande anterior, simplemente puedo agregar la nueva cláusula sin pensarlo mucho. En unif/else , no puedo estar tan seguro de no estropear algo, especialmente si la lógica es más complicada.

También señalaré que su ejemplo simple tampoco es tan simple. Una prueba para un valor no binario no es lo mismo que la inversa de esa prueba con resultados invertidos.

cbojar
fuente
1
Mi edición de la pregunta aborda este caso. Cuando una restricción que debe cumplirse lógicamente para todos los códigos siguientes, como una verificación nula (o cualquier otra protección), estoy de acuerdo en que la omisión de un elsebloque es esencial para la legibilidad.
Selali Adobor
1
Su primera versión es difícil de entender, pero no creo que sea necesaria al usar if / else. Vea cómo lo escribiría: gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Winston Ewert
@ WinstonEwert Vea las ediciones para responder a sus comentarios.
cbojar
Su edición hace algunos puntos válidos. Y ciertamente creo que las cláusulas de guardia tienen un lugar. Pero en el caso general, estoy a favor de otras cláusulas.
Winston Ewert
0

Has simplificado demasiado tu ejemplo. Cualquiera de las alternativas puede ser más legible, dependiendo de la situación más amplia: específicamente, depende de si una de las dos ramas de la condición es anormal . Déjame mostrarte: en un caso donde cualquiera de las ramas es igualmente probable, ...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... es más legible que ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... porque el primero exhibe una estructura paralela y el segundo no. Pero, cuando la mayor parte de la función se dedica a una tarea y solo necesita verificar algunas condiciones previas primero, ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... es más legible que ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

... porque deliberadamente no establecer una estructura paralela proporciona una pista para un futuro lector de que obtener una entrada que "realmente escriba dos" aquí es anormal . (En la vida real, ProcessInputDefinitelyOfTypeOneno sería una función separada, por lo que la diferencia sería aún más marcada).

zwol
fuente
Sería mejor elegir un ejemplo más concreto para el segundo caso. Mi reacción inmediata es: "no puede ser tan anormal si continúas y lo procesas de todos modos".
Winston Ewert
@ WinstonEwert anormal es quizás el término incorrecto. Pero estoy de acuerdo con Zack, que si tiene un defecto (caso1) y una excepción, debe escribirse como » if-> Hacer lo excepcional y dejar« como una estrategia de retorno temprano . Piense en el código, en el que el valor predeterminado incluye más verificaciones, sería más rápido manejar el caso excepcional primero.
Thomas Junk
Esto parece estar cayendo en el caso del que estaba hablando when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). Lógicamente, cualquier trabajo ProcessInputOfTypeOnedepende del hecho de que !InputIsReallyTypeTwo(input), por lo tanto, necesitamos detectarlo fuera de nuestro procesamiento normal. Normalmente ProcessInputOfTypeTwo(input);sería realmente throw ICan'tProccessThatExceptionlo que haría más evidente la similitud.
Selali Adobor
@ WinstonEwert Tal vez podría haber dicho eso mejor, sí. Estaba pensando en una situación que surge mucho al codificar los tokenizadores a mano: en CSS, por ejemplo, la secuencia de caracteres " u+" generalmente introduce un token de "rango unicode", pero si el siguiente carácter no es un dígito hexadecimal, entonces uno necesita hacer una copia de seguridad y procesar la 'u' como un identificador en su lugar. Entonces, el bucle principal del escáner se despacha para "escanear un token de rango unicode" de manera algo imprecisa, y comienza con "... si estamos en este caso especial inusual, haga una copia de seguridad, luego llame a la subrutina de escanear un identificador".
zwol
@AssortedTrailmix Incluso si el ejemplo en el que estaba pensando no hubiera sido de una base de código heredada en la que no se pueden usar excepciones, esta no es una condición de error , es solo que el código que llama ProcessInputOfTypeOneutiliza una verificación imprecisa pero rápida que a veces capturas tipo dos en su lugar.
zwol
0

Sí, hay un aumento en la complejidad porque la cláusula else es redundante . Por lo tanto, es mejor dejar de lado para mantener el código delgado y malo. Está en el mismo acorde que omitir thiscalificadores redundantes (Java, C ++, C #) y omitir paréntesis en las returndeclaraciones, y así sucesivamente.

Un buen programador piensa "¿qué puedo agregar?" mientras que un gran programador piensa "¿qué puedo eliminar?".

vidstige
fuente
1
Cuando veo la omisión del elsebloque, si se explica de una sola vez (lo que no es frecuente) a menudo es con este tipo de razonamiento, entonces veo +1 para presentar el argumento "predeterminado", pero no lo hago. encuentra que es un razonamiento lo suficientemente fuerte. A good programmer things "what can I add?" while a great programmer things "what can I remove?".parece ser un adagio común de que muchas personas se extienden en exceso sin una cuidadosa consideración, y personalmente considero que este es uno de esos casos.
Selali Adobor
Sí, al leer tu pregunta, inmediatamente sentí que ya te habías decidido, pero quería confirmación. Obviamente, esta respuesta no es lo que quería, pero a veces es importante tener en cuenta opiniones con las que no está de acuerdo. Tal vez hay algo también?
vidstige
-1

Me di cuenta, cambiando de opinión sobre este caso.

Cuando me hicieron esta pregunta hace aproximadamente un año, habría respondido algo como:

»Debería haber solo uno entryy uno exitpara un método« Y con esto en mente, habría sugerido refactorizar el código para:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

Desde el punto de vista de la comunicación, está claro qué se debe hacer. Ifalgo es el caso, manipular el resultado de una manera , else manipularlo de otra manera .

Pero esto implica una innecesaria else . El elseexpresa el caso predeterminado . Entonces, en un segundo paso, podría refactorizarlo para

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

Entonces surge la pregunta: ¿Por qué usar una variable temporal? El siguiente paso de refactorización conduce a:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

Entonces esto está claro en lo que hace. Incluso iría un paso más allá:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

O para los débiles de corazón :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Podrías leerlo así: »CanLeaveWithoutUmbrella devuelve un bool . Si no sucede nada especial, devuelve falso «Esta es la primera lectura, de arriba a abajo.

Y luego "analiza" la condición »Si se cumple la condición, devuelve verdadero « Podría omitir por completo el condicional y haber entendido lo que hace esta función en el caso predeterminado . Tu ojo y tu mente solo tienen que leer algunas letras en el lado izquierdo, sin leer la parte de la derecha.

Tengo la impresión de que el otro indica la intención más directamente, al afirmar el hecho de que el código en ambos bloques está directamente relacionado.

Si, eso es correcto. Pero esa información es redundante . Tiene un caso predeterminado y una "excepción" que está cubierta por la ifdeclaración.

Al tratar con estructuras más complejas, elegiría otra estrategia, omitiendo el ifo es el hermano feo switchen absoluto.

Thomas Junk
fuente
La redundancia de +1 definitivamente muestra un aumento no solo en la complejidad, sino también en la confusión. Sé que siempre terminé teniendo que verificar el código escrito exactamente así debido a la redundancia.
dietbuddha
1
¿Podría eliminar los casos después de que el caso comience So this is clear in what it does...y expandirlo? (Los casos anteriores también son de relevancia cuestionable). Digo esto porque es lo que está preguntando la pregunta que examinamos. Has declarado tu postura, omitiendo el bloque else, y en realidad es la postura la que necesita más explicación (y la que me hizo hacer esta pregunta). De la pregunta:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
@AssortedTrailmix ¡Seguro! ¿Sobre qué debo elaborar exactamente? Dado que su pregunta inicial fue "¿Los bloques más aumentan la complejidad del código?" Y mi respuesta es: sí. En este caso podría ser omitido.
Thomas Junk
¡Y no! No entiendo el voto negativo.
Thomas Junk
-1

Para resumir, en este caso, deshacerse de la elsepalabra clave es algo bueno. Aquí es totalmente redundante y, dependiendo de cómo formatee su código, agregará una o tres líneas más completamente inútiles a su código. Considere lo que hay en el ifbloque:

return true;

Por lo tanto, si ifse ingresara el bloque, el resto de la función, por definición, no se ejecuta. Pero si no se ingresa, ya que no hay más ifdeclaraciones, se ejecuta el resto de la función. Sería totalmente diferente si una returndeclaración no estuviera en el ifbloque, en cuyo caso la presencia o ausencia de unelse bloque tendría un impacto, pero aquí, no tendría un impacto.

En su pregunta, después de invertir su ifcondición y omitirla else, declaró lo siguiente:

Alguien ahora puede agregar un nuevo bloque if basado en una condición después del primer ejemplo sin reconocer inmediatamente correctamente que la primera condición impone una restricción a su propia condición.

Necesitan leer el código un poco más de cerca entonces. Utilizamos lenguajes de alto nivel y una organización clara del código para mitigar estos problemas, pero agregar un elsebloque completamente redundante agrega "ruido" y "desorden" al código, y tampoco deberíamos tener que invertir o reinvertir nuestras ifcondiciones. Si no pueden notar la diferencia, entonces no saben lo que están haciendo. Si pueden notar la diferencia, siempre pueden invertir el originalif condiciones ellos mismos.

Olvidé mencionar un caso en el que estoy de acuerdo con la omisión de un bloque else, y es cuando uso un bloque if para aplicar una restricción que debe cumplirse lógicamente para todos los códigos siguientes, como una verificación nula (o cualquier otra protección )

Sin embargo, eso es esencialmente lo que está sucediendo aquí. Está volviendo del ifbloque, por lo que la ifcondición de evaluación false es una restricción que debe cumplirse lógicamente para todos los códigos siguientes.

¿Hay un aumento en la complejidad introducido al no incluir el bloque else?

No, constituirá una disminución en la complejidad de su código, e incluso puede constituir una disminución en el código compilado, dependiendo de si se realizarán o no ciertas optimizaciones súper triviales.

Panzercrisis
fuente
2
"Necesitan leer el código un poco más de cerca entonces". El estilo de codificación existe para que los programadores puedan prestar menos atención a los detalles meticulosos y más atención a lo que realmente están haciendo. Si tu estilo te hace prestar atención a esos detalles innecesariamente, es un mal estilo. "... líneas completamente inútiles ..." No son inútiles: te permiten ver de un vistazo cuál es la estructura, sin tener que perder el tiempo razonando sobre lo que sucedería si esta parte o esa parte fueran ejecutadas.
Michael Shaw
@MichaelShaw Creo que la respuesta de Aviv Cohn entra en lo que estoy hablando.
Panzercrisis
-2

En el ejemplo específico que diste, lo hace.

  • Obliga a un revisor a leer el código nuevamente para asegurarse de que no sea un error.
  • Agrega complejidad ciclomática (innecesaria) porque agrega un nodo al gráfico de flujo.

Creo que no podría ser más simple que esto:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}
Tulains Córdova
fuente
66
Me refiero específicamente al hecho de que este ejemplo muy simplificado puede reescribirse para eliminar la ifdeclaración. De hecho, desaconsejo explícitamente una mayor simplificación utilizando el código exacto que utilizó. Además, la complejidad ciclomática no aumenta con el elsebloque.
Selali Adobor
-4

Siempre trato de escribir mi código de manera que la intención sea lo más clara posible. Su ejemplo carece de contexto, así que lo modificaré un poco:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Nuestra intención parece ser que podemos irnos sin paraguas cuando el cielo es azul. Sin embargo, esa simple intención se pierde en un mar de código. Hay varias formas en que podemos facilitar su comprensión.

En primer lugar, está su pregunta sobre la elsesucursal. No me importa de ninguna manera, pero tiendo a dejar de lado else. Entiendo el argumento acerca de ser explícito, pero mi opinión es que las ifdeclaraciones deben envolver ramas 'opcionales', como la return true. No llamaría alreturn false rama 'opcional', ya que actúa como nuestro valor predeterminado. De cualquier manera, veo esto como un problema muy menor y mucho menos importante que los siguientes:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

¡Una cuestión mucho más importante es que sus sucursales están haciendo demasiado! Las ramas, como todo, deberían ser "lo más simple posible, pero no más". En este caso, ha utilizado una ifdeclaración, que se bifurca entre bloques de código (colecciones de declaraciones) cuando todo lo que realmente necesita para bifurcarse son expresiones (valores). Esto está claro ya que a) sus bloques de código solo contienen declaraciones únicas yb) son la misma declaración ( return). Podemos usar el operador ternario ?:para reducir la rama solo a la parte que es diferente:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Ahora llegamos a la redundancia obvia a la que nuestro resultado es idéntico this.color == Color.Blue. Sé que su pregunta nos pide que ignoremos esto, pero creo que es realmente importante señalar que esta es una forma de pensar de primer orden y procedimiento: está desglosando los valores de entrada y construyendo algunos valores de salida. Eso está bien, pero si es posible es mucho más poderoso pensar en términos de relaciones o transformaciones entre la entrada y la salida. En este caso, la relación es obviamente que son iguales, pero a menudo veo un código de procedimiento complicado como este, que oculta alguna relación simple. Avancemos y hagamos esta simplificación:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

Lo siguiente que señalaría es que su API contiene un doble negativo: es posible CanLeaveWithoutUmbrellaque lo sea false. Esto, por supuesto, se simplifica a NeedUmbrellaser true, así que hagamos eso:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Ahora podemos comenzar a pensar en su estilo de codificación. Parece que está usando un lenguaje orientado a objetos, pero al usar ifdeclaraciones para ramificarse entre bloques de código, ha terminado escribiendo procedimientos código de .

En el código orientado a objetos, todos los bloques de código son métodos y todas las ramificaciones se realizan a través del despacho dinámico , por ejemplo. usando clases o prototipos. Es discutible si eso simplifica las cosas, pero debería hacer que su código sea más coherente con el resto del lenguaje:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

Tenga en cuenta que el uso de operadores ternarios para ramificar entre expresiones es común en la programación funcional .

Warbo
fuente
El primer párrafo de la respuesta parece estar en camino de responder la pregunta, pero inmediatamente diverge en el tema exacto que pido explícitamente que uno ignore para este ejemplo muy simple . A partir de la pregunta: I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). De hecho, usa la línea exacta de código que menciono. Además, aunque esta parte de la pregunta está fuera de tema, diría que está yendo demasiado lejos en su inferencia. Has cambiado radicalmente el código.
Selali Adobor
@AssortedTrailmix Esto es puramente una cuestión de estilo. Tiene sentido preocuparse por el estilo y tiene sentido ignorarlo (centrándose solo en los resultados). No creo que tiene sentido a la atención acerca return false;vs else { return false; }mientras no preocuparse por la polaridad rama, envío dinámico, ternarios, etc. Si estás escribiendo código de procedimientos en un lenguaje orientado a objetos, el cambio radical es necesario;)
Warbo
Puede decir algo así en general, pero no es aplicable cuando responde una pregunta con parámetros bien definidos (especialmente cuando sin respetar esos parámetros puede "abstraer" toda la pregunta). También diría que la última oración es simplemente errónea. OOP se trata de "abstracción procesal", no de "obliteración procesal". Diría que el hecho de que haya pasado de una línea a un número infinito de clases (una para cada color) muestra por qué. Además, todavía me pregunto qué envío dinámico debería tener que ver con una if/elsedeclaración, y qué es la polaridad de la rama.
Selali Adobor
Nunca dije que la solución OO es mejor (de hecho, prefiero la programación funcional), simplemente dije que es más consistente con el lenguaje. Por "polaridad" quise decir qué cosa es "verdadera" y cuál es "falsa" (lo cambié por "NeedUmbrella"). En el diseño OO, todo el flujo de control está determinado por el despacho dinámico. Por ejemplo, Smalltalk no permite nada más en.wikipedia.org/wiki/Smalltalk#Control_structures (también vea c2.com/cgi/wiki?HeInventedTheTerm )
Warbo
2
¡Hubiera votado a favor hasta el último párrafo (sobre OO), lo que le valió un voto negativo! Ese es exactamente el tipo de uso excesivo e irreflexivo de las construcciones OO que produce código de ravioles , que es tan ilegible como el espagueti.
zwol