¿Por qué el software no es abstracto en una escala mayor?

6

Considere el siguiente ejemplo:

El usuario desea un programa para calcular algunos números de fibonacci. Suena bastante fácil. pseudocódigo:

stdout.write("How many fibonacci numbers do you want to calculate? ")
int count = int(stdin.readline())
while count>0:
    stdout.writeline(calculate_next_fibonacci_number())
    count--

Incluso un programa muy simple como este ya es defectuoso:

  • El programa escribe en stdout. Eso no es realmente lo que pretendía el programador, ¿verdad? La intención es mostrar un número de números al usuario, no para escribir un texto a la salida estándar, no hay garantía de que el usuario vea el texto que está escrito en la salida estándar.
  • De forma similar, la entrada del usuario se lee desde la entrada estándar, que es una interfaz de texto (o archivo, si lo prefiere), cuando en realidad el programa requiere un número, no texto. Esto parece fundamentalmente incorrecto.

Pensemos en lo que intentamos hacer de manera más abstracta. Queremos:

  • Calcula un grupo de números. Cuántos depende del usuario.
  • muestra los resultados al usuario.

¿Por qué, entonces, no escribimos código exactamente así? (La mayoría) los lenguajes de programación proporcionan esta cosa llamada "función", que acepta parámetros y los usa para hacer algo. ¿No suena exactamente como lo que estamos tratando de hacer?

void display_fibonacci_numbers(
        int number "how many fibonacci numbers to calculate"):

        Sequence<int> numbers
        while number>0:
            numbers.append(calculate_next_fibonacci_number())
            number--

        notify(numbers)

display_fibonacci_numbers()

Este código está, por supuesto, incompleto: la función notify no se implementa en ninguna parte, y el usuario necesita alguna forma de ingresar un número. Me imagino que el sistema operativo o el administrador de escritorio del usuario o lo que sea que se ocupe de eso: podría mostrar un terminal, generar una GUI, podría decirle al usuario que escriba el número en el aire con su nariz; de cualquier manera, no me concierne ( debería no) al programador.

Creo que el software debería ser más abstracto.

Algunos ejemplos más.

  • comunicación entre procesos. ¿Cómo lo haces? Sockets? Señales? DBus? Archivos? El objetivo no es utilizar sockets, señales o dbus, sino comunicarse con otro proceso. ¿Por qué no algo como Process.from_name("Music player").play_random_song() ?
  • Descargando archivos. %código%? ¿Por qué no local_file.write(remote_file.read()) , que podría, según la configuración del sistema, iniciar la descarga de inmediato, pero con una prioridad baja para no ralentizar otras descargas, o agregarla a la cola de descargas para descargarla más tarde, o lo que sea?
  • Rutas de archivos. ¿Por qué diablos son las rutas de archivos todavía cadenas (al menos en la mayoría de los idiomas)? ¿Por qué tenemos que tratar si el separador de ruta es download(url) o / , si es \ , . , o lo que sea? ¿Por qué no hay una clase .. que se encargue de esto?

Yendo un paso más allá, ¿por qué no aplicar el concepto de tipificación de pato a los módulos? Por ejemplo, en Python, el HTML a menudo se analiza utilizando el módulo FilePath :

from bs4 import BeautifulSoup

page= BeautifulSoup(urlopen('http://test.at'))

Nuevamente, el objetivo del programador no era usar BeautifulSoup, sino analizar HTML. ¿Por qué, entonces, le diría explícitamente a su código que use el módulo BeautifulSoup? Lo que realmente quiere es

import HTMLParser
page= HTMLParser.parse(urlopen('http://test.at'))

Duck-typing para módulos: ¿A quién le importa qué módulo es mientras haga lo que yo quiero?

¿Por qué la programación no es así? ¿Estoy pasando por alto algo, algo que hace todo esto imposible (o impráctico)?

    
pregunta Aran-Fey 09.12.2014 - 14:35

9 respuestas

21
  

¿Por qué la programación no es así?

¿Por qué crees eso? En realidad, la programación es así. Por supuesto, no se puede hacer eso en ensamblador. Por lo tanto, inventamos lenguajes que pueden ser más similares a los pensamientos humanos.

Los programadores buenos programan por igual los ejemplos que dieron. Pero no es tan fácil. Primero hay que entender, cuál es el requisito. Esto ya es lo suficientemente difícil y la mayoría de las personas no pueden elaborar correctamente lo que necesitan; por lo tanto, un buen diseñador de software necesita entender realmente lo que la gente quiere, no lo que dicen.

Después de hacer eso, necesitas ver los conceptos detrás de las cosas del mundo real. Eso es bastante filosófico y necesita mucha experiencia. Y luego, después de hacer esto, tiene que traducir estos resultados a un idioma de su elección, trabajando en torno a las incapacidades que tiene cada idioma y elija el nivel de abstracción correcto (no desea sobrepasar nada, ¿verdad?).

No todas las personas son buenas en estos pasos o incluso saben de ellos. Incluso en pequeños ejemplos se puede ver que no es trivial. Tomemos su función display_fibonacci_numbers y veamos qué podemos mejorar allí (aunque no sea codereview.stackexchange aquí).

void display_fibonacci_numbers(

La denominación en realidad no es muy precisa porque está muy especificado en el tipo de números que mostrará, ¿verdad? Eso debe ser descrito por el nombre de la función.

    int number "how many fibonacci numbers to calculate"):

¿Por qué es un int ? Eso es muy concreto y hasta equivocado, porque los ints pueden tener valores negativos, ¿no? Entonces, lo que realmente podría desear es un tipo que se pueda usar para calcular los números de fibonacci. Tal vez un natural number ?

    Sequence<int> numbers
    while number>0:
        numbers.append(calculate_next_fibonacci_number())
        number--

¿Eso realmente describe lo que pretendes hacer? En lenguaje natural (que a menudo es una buena aproximación) diríamos "crear una lista del número especificado de números de fibonacci". No puedo leer nada sobre la disminución de un número aquí, no puedo leer nada sobre un valor estático 0 aquí. Tampoco se trata de una función "calculada next _fibonacci_number ()".

Por lo tanto, en Scala, p. ej. escribe el código como List.fill(number)(randomFibonacci())

No se lee exactamente como en lenguaje natural, pero al menos contiene la misma información.

Quería darte estos ejemplos para mostrarte que es difícil escribir software de esa manera. Debe tener experiencias, dedicar una buena cantidad de tiempo a pensar en las cosas, ser capaz de encontrar los conceptos y abstracciones correctos y, ENTONCES, tener un lenguaje que no le impida expresarlos de manera concisa. Esto es, ¿por qué no mucha gente programa así? Y peor aún: los humanos se acostumbran a las cosas. Entonces, si uno siempre programa en un idioma que le impide realizar el último paso, tal vez continúe programando en un nuevo idioma como en el anterior.

    
respondido por el valenterry 09.12.2014 - 15:01
14
  

¿Por qué, entonces, no escribimos código exactamente así?

Lo hacemos. Su ejemplo de Fibonacci es una clara violación del principio de responsabilidad única. Un método debe ser responsable de ser un generador que proporcione la secuencia de Fibonacci. Las otras partes son responsables de quitarle N valores, omitir N valores, obtener información, etc.

  

Me imagino que el sistema operativo o el administrador de escritorio del usuario o lo que sea que se ocupe de eso: podría mostrar un terminal, podría generar una GUI, podría decirle al usuario que escriba el número en el aire con su nariz; de cualquier manera no me concierne (no debería) al programador.

Algunas veces no le concierne al programador. En esos casos, utiliza una abstracción de la interfaz de usuario que hace ese pegamento para convertir "obtener un número" e "imprimir estos números" en implementaciones específicas de la plataforma. Estos ya existen y son de uso común.

Pero te estás perdiendo el punto.

La gran mayoría del trabajo de un programador no es hacer estos programas completos, sino pegar fragmentos dispares de código existente. Ya hay código para hacer la secuencia de Fibonacci. Ya hay código para tomar la primera N de una secuencia. Ya hay código para imprimir números en esta plataforma.

La única razón por la que estás allí es porque alguien quiere todos esos bits de código juntos. ¿Por qué no fueron los primeros primos N en su lugar? Los detalles específicos de cómo obtener una entrada y una salida de impresión son requisitos tan vitales como la secuencia que se debe utilizar.

  

¿Por qué no algo como Process.from_name ("Reproductor de música"). play_random_song ()

AppleScript ya hace cosas como esta. WCF de muchas maneras ya abstrae la mecánica de la comunicación de la semántica de la comunicación.

  

¿Por qué no descargar (url)

C # (y otros) ya tienen bibliotecas para hacer esto. Es cierto que ya tienen una semántica bien definida, porque la configuración es vil. Es código sin ningún marco / proceso para hacer código con calidad. Peor aún, elimina cualquier calidad (y, a menudo, el rendimiento) de su código real.

  

¿Por qué no hay ninguna clase FilePath que se encargue de esto?

C # (y otros) ya tienen bibliotecas para hacer esto.

  

Yendo un paso más allá, ¿por qué no aplicar el concepto de escritura de pato a los módulos?

Algunos lenguajes (más esotéricos) lo hacen. Los módulos se tratan como objetos grandes, que pueden tener sus propias interfaces, ser instanciados, parametrizados, etc.

El problema es que la gran mayoría de "cómo funciona" está definida por la interfaz del módulo. Una implementación tendrá secuencias de archivos de cierre automático y otra utilizará Open y Close . Estas son interfaces estrictamente diferentes.

  

¿Por qué la programación no es así?

Hay un barco lleno de razones.

Probablemente el más convincente es este tipo de escenario . No importa que el hecho de tener el control de su sistema operativo de entrada / salida haga que los programadores hagan mejor el código más rápido, algunas personas de negocios querrán que haga algo diferente para "diferenciar".

    
respondido por el Telastyn 10.12.2014 - 16:06
12

¿Por qué los programas no son más abstractos?

En pocas palabras: porque cuando te pones a ello, las abstracciones no hacen nada. Las implementaciones hacen. Y cuanto más abstracto sea su programa, cuanto más divorciado esté de lo que realmente está sucediendo, más difícil será comprender lo que hace el código.

Al principio, para un ingenuo desarrollador, ciertamente parece enfocarse en el objetivo de alto nivel de lo que se supone que debe hacer el código y "abstraer los detalles de la implementación" hace que el código sea más fácil de entender, pero respeto que cualquiera que crea que esto tiene muy poca experiencia en la depuración. Cuando algo va mal en su código, especialmente si ya se ha publicado un código y tiene clientes que no están contentos con eso y quieren una solución ahora , más rápido podrá encontrar dónde está el problema es el mejor. Y tener que cavar a través de capas sobre capas sobre capas de abstracciones hace que esto sea más difícil, no más fácil.

Dado que el ciclo de vida del desarrollo de software moderno pasa mucho más tiempo en el mantenimiento que en el desarrollo original, cualquier cosa que favorezca el desarrollo original rápido y la alta abstracción a expensas del mantenimiento, la legibilidad y la capacidad de depuración deben considerarse un meta-ejemplo de optimización prematura. / p>

No me malinterpretes. No estoy en contra de la abstracción donde tiene sentido. Pero con demasiada frecuencia ves que la gente agrega más y más abstracción solo por el bien de la abstracción, y convierte el código en un lío de complejidad en lugar de reducir la complejidad. Recuerde el consejo que se atribuyó a Einstein: todo debería ser lo más simple posible, pero no más simple.

    
respondido por el Mason Wheeler 09.12.2014 - 14:57
7

Lo más importante que te falta es que la abstracción tiene costos y beneficios.

  

"Todos los problemas en informática se pueden resolver con otro nivel de indirección, excepto, por supuesto, por el problema de demasiadas indirecciones". -David Wheeler

A veces el costo es muy pequeño, como en su ejemplo de fibonacci, así que incluso si no nos gana nada, no importa mucho, porque tampoco nos costó mucho. Ajustar unas pocas líneas de código en una función no es algo que requiera mucho tiempo o esfuerzo. A veces el costo es alto, y luego tenemos que asegurarnos de que obtengamos algo significativo.

Si decidiéramos hacer una interfaz de análisis HTML abstracta, dedicar tiempo a diseñarla y luego implementar una instancia de esa interfaz que usara BeautifulSoup, ¿hemos perdido nuestro tiempo? Probablemente, si solo usamos nuestra elegante interfaz para llamar a BeautifulSoup. (Tenga en cuenta que el reemplazo propuesto para BeautifulSoup en su pregunta esencialmente no hizo nada por usted, excepto cambiar el nombre de BeautifulSoup a HTMLParser).

Envolver algo que no necesita ser envuelto en primer lugar es una pérdida de tiempo.

Por otro lado, si vamos a escribir un programa que use BeautifulSoup para la mayoría de los análisis, pero a veces realmente necesita las capacidades de análisis de UglyOatmeal o SloppySandwich, una interfaz que los envuelve puede ahorrarnos mucho tiempo y esfuerzo. .

Parece que piensas que los detalles no deberían preocuparte como programador. Esto solo es a veces cierto; algunos detalles ya son manejados por el hardware, el sistema operativo, el lenguaje de programación y las bibliotecas. Otros detalles importan fundamentalmente de formas que los hacen imposibles de manejar automáticamente; en su ejemplo de fibonacci, es importante para el comportamiento del programa si el tipo de número usado es un int o un tipo que puede manejar números más grandes. A veces, los detalles de la representación de algo hacen una gran diferencia en el rendimiento.

Es bueno cuando las cosas se manejan de forma automática para nosotros, pero generalmente hay un costo de algún tipo u otro. A menudo, el costo es el rendimiento y, a menudo, la falta de control sobre algo.

Incluso con idiomas hermosos y bibliotecas asombrosas, no todos los detalles se pueden manejar de manera automática, y el software fundamentalmente se trata de manejar una enorme cantidad de detalles, por lo que no debemos sorprendernos o molestarnos al descubrir que algunos de ellos no pudieron ser manejado por nosotros

No es que necesitemos una abstracción de más , necesitamos las abstracciones correctas en los lugares correctos. La abstracción es una de nuestras herramientas más poderosas en la programación, pero hay formas de hacer un mal uso o esperar irrealmente a hacer magia.

    
respondido por el Michael Shaw 10.12.2014 - 23:58
6

Has descubierto una buena factorización. Felicidades.

¿Por qué más personas no programan así? Debido a que es difícil ver la ventaja de hacer las cosas de una manera que parece más esfuerzo y solo se pagará más adelante, cuando los requisitos cambien. Las personas son miopes; incluso los que pueden pensar lo suficientemente abstractos para convertirse en programadores son cortos de vista. Algunos de ellos son un poco menos cortos de vista, y tienden a ser los que inventan el raro truco o patrón que finalmente logra establecerse y elevar un poco la calidad del software en general.

Luego, alguien inventa un canal de distribución nuevo y brillante con un extraño lenguaje de programación ad-hoc kludgy que lo acompaña, y todo el progreso sale rápidamente de la ventana. No te sientas mal, así es como es el mundo :)

    
respondido por el Kilian Foth 09.12.2014 - 14:39
3

La ley de rendimientos decrecientes se aplica a la abstracción como cualquier otra cosa. ¿Es mejor configurar un dispositivo utilizando cables de puente o interruptores DIP? Probablemente los interruptores DIP, que son una abstracción sobre la conexión de cables de puente. ¿Es mejor programar en lenguaje máquina o lenguaje ensamblador? La respuesta es casi seguramente el lenguaje ensamblador. Es difícil para mí pensar en un escenario en el que no querría al menos usar un ensamblador para generar su lenguaje de máquina.

Sin embargo, estamos mucho más allá de las abstracciones leves que acabo de sugerir, y diría que muchos, tal vez la mayoría, los programadores están trabajando a un nivel de abstracción más allá del punto de rendimientos decrecientes ya .

Un ejemplo: trabajo con varios sistemas PAAS que intentan hacer la tarea difícil de integración con otros sistemas más abstracta y más fácil. Continuamente me pregunto qué es lo que realmente sucede debajo de la fachada de apariencia limpia que presentan estos sistemas:

  • ¿Qué significa la palabra "valor en blanco" en esa etiqueta de casilla de verificación? ¿Significa que la fila de origen está presente pero el valor de la columna es nulo? ¿Significa que la fila fuente está ausente? ¿Significa ambos? ¿Esta herramienta PAAS en particular tiene un concepto de verdadero / falso / nulo? Si es así, ¿qué campos son anulables (y esta configuración se encuentra en un diálogo en algún lugar, en un esfuerzo por hacer que la experiencia del usuario sea más abstracta)?

  • Cuando selecciono la opción "agregar registros a la base de datos de destino", ¿esta actualización de registros también? No veo una opción de "actualización" ... así que "agregar registros" también debe hacer actualizaciones ... ¿verdad? (Esto puede parecer un ejemplo ridículo, pero es real. Y sí, terminé en Google tratando de determinar si "agregar" realmente significa "agregar / actualizar" para una configuración particular en una herramienta PAAS en particular. ¿Tu idea de una buena experiencia de desarrollador?)

Es difícil responder a estas preguntas, ya que todas esas cosas del programador se han ocultado cuidadosamente, bloqueadas en implementaciones de caja negra, etc., en el nombre de la presentación de una interfaz de usuario abstracta. Terminé teniendo dificultades para formular las preguntas necesarias (ya sea dirigidas a Google o al soporte técnico). Me pregunto acerca de cosas como la nulabilidad y las operaciones de "elevación", pero mis interlocutores están evitando activamente el uso de términos técnicos de bajo nivel. Pero este tipo de preguntas necesitan para ser respondidas para hacer la integración ...

Y mi último punto apunta a la posibilidad de que, a medida que desarrollamos un lenguaje de computación más compartido, podamos trabajar a un nivel más abstracto. Si los no programadores (incluidos los diseñadores de GUI) usarían términos estándar como nullable, "upsert", clave externa, etc., tal vez muchas de las cosas que se hacen actualmente utilizando el código podrían hacerse gráficamente, o de una manera más abstracta. Pero cuando la gente dice "agregar" en lugar de "upert", porque "upsert" es un lenguaje de programación aterrador, bueno, tengo que recurrir a mis antiguas habilidades y herramientas de bajo nivel para realmente hacer el trabajo.

    
respondido por el user1172763 10.12.2014 - 15:57
3

Lo que estás preguntando es, "¿por qué no podemos usar Z o UML para realizar un prototipo completo y diseñar un sistema? "

Y la respuesta a que es: "Porque, mientras podría construir algo que construya un sistema basado en una especificación UML o Z, verificando spec se vuelve tan difícil (si no más) que implementar la espec. "

    
respondido por el warren 16.12.2014 - 20:42
1

Puede comprar muchas piezas prefabricadas en la tienda, pero todavía hay mecánicos artesanos que son necesarios para fabricar piezas que no existen. Las abstracciones están ahí, pero no siempre para cada entorno. En algún momento, alguien tendrá que voltear un poco de manera que no haya ninguna abstracción en la forma de una función disponible.

Voy a usar tu cadena de ruta de archivo como ejemplo:

  

Rutas de archivos. ¿Por qué diablos son las rutas de archivos todavía cadenas (en la mayoría   idiomas, al menos)? ¿Por qué tenemos que lidiar con si el camino   separador es / o \, si es., .., o lo que sea? Porque no hay   ¿Clase de FilePath que se encarga de esto?

Algunas veces estas abstracciones existen, pero no en el lenguaje de programación en sí. Microsoft Windows, .NET Framework y la familia de lenguajes de programación que usan .NET y se ejecutan en Windows tienen esta capacidad. enlace

No es realmente parte de los lenguajes de programación (VB.NET, C #, F #, etc.), sino un marco. Veo este .NET Framework como una abstracción de todas las tuberías de Windows que son necesarias y no son realmente parte de la lógica o los algoritmos que necesitamos crear en nuestro código. El programador puede elegir usar este objeto desde el marco .NET o crear el suyo propio. Su argumento parece que crear la suya es una mala idea porque hay algo mejor que está más disponible, entonces, ¿por qué le da al desarrollador la opción? No estoy seguro de cómo quitar esa opción. ¿Impedir que una cadena de ubicación de archivos pueda interactuar con el sistema de archivos de alguna forma? ¿Solo puede programar para Windows si utiliza el marco y la familia de idiomas apropiados? ¿Cómo se obtiene la URL de la web? ¿Tiene que venir en forma de un objeto .NET IO?

    
respondido por el JeffO 10.12.2014 - 15:41
1

Muchos de sus ejemplos parecen ser problemas en la búsqueda de una solución. En un escenario del mundo real, su función de número de Fibonacci sería en realidad una función que toma 1 o 2 (para subconjuntos arbitrarios de la secuencia de Fibonacci) los parámetros int y devuelve una lista de enteros, y el proceso de llamada se encargaría de obtener la entrada y la visualización La salida. Si tiene el código que proporcionó en el mundo real, es porque el desarrollador nunca tuvo la intención de que fuera algo distinto a algo que ejecutó desde la línea de comandos.

Para muchos de tus otros ejemplos, la respuesta es a menudo simplemente "porque eventualmente necesitarás instanciar algo y hacer las cosas". En algún momento, para que se ejecute el código, algo tiene que ser una implementación real. En ese caso, es una biblioteca de sistema de archivos (además de la biblioteca de C # Telastyn en la lista, hay una os en Python, y las bibliotecas relacionadas con el archivo Java (en java.io y java.nio ). Agregar otra capa de abstracción no hará que nada en los ejemplos que mencionaste mejor .

Hay un acto de equilibrio que interviene en el proceso de toma de decisiones sobre si se debe utilizar algún tipo de abstracción. ¿Cuánto más complicado hace que mantener el código? (como se explica en Mason Wheeler ) ¿En qué medida esto facilita la escritura del código? ¿Los beneficios que espero obtener de esto realmente justifican el tiempo extra que se necesita para poner esto en práctica? Y quizás la pregunta más importante: ¿Realmente anticipo que alguna vez haya más de 1 implementación aquí?

Encuentras que llegas a un punto, a menudo mucho antes de lo que crees, donde agregar más abstracción no vale la pena.

    
respondido por el Eric Hydrick 27.12.2014 - 00:34

Lea otras preguntas en las etiquetas