2010-02-24 18 views
18

Hay un montón de rumores acerca de MooseX::Method::Signatures e incluso antes de eso, los módulos como Params::Validate que están diseñados para escribir comprueban todos los argumentos a métodos o funciones. Estoy considerando usar el primero para todo mi futuro código Perl, tanto personal como en mi lugar de trabajo. Pero no estoy seguro de si vale la pena el esfuerzo.¿Vale la pena verificar los argumentos de la función Perl?

Estoy pensando en todo el código de Perl que he visto (y escrito) antes de que no realice dicha comprobación. Muy rara vez veo un módulo de hacer esto:

my ($a, $b) = @_; 
defined $a or croak '$a must be defined!'; 
!ref $a or croak '$a must be a scalar!"; 
... 
@_ == 2 or croak "Too many arguments!"; 

Tal vez porque es simplemente demasiado trabajo sin algún tipo de módulo de ayuda, pero tal vez porque en la práctica no enviamos el exceso de argumentos a las funciones, y no hago envíe los arreglos de matriz a los métodos que esperan escalares, o si lo hacemos, tenemos use warnings; y nos enteramos rápidamente de ello - un enfoque de duck typing.

Por lo tanto, ¿la comprobación del tipo de Perl vale la pena el rendimiento alcanzado, o sus puntos fuertes se muestran predominantemente en lenguajes compilados y fuertemente tipados como C o Java?

Me interesan las respuestas de cualquier persona que tenga experiencia escribiendo Perl que utiliza estos módulos y ha visto los beneficios (o no) de su uso; si su empresa/proyecto tiene alguna política relacionada con la verificación de tipos; y cualquier problema con la verificación de tipos y el rendimiento.

ACTUALIZACIÓN: He leído un artículo interesante sobre el tema recientemente, llamado Strong Testing vs. Strong Typing. Ignorando el ligero sesgo de Python, esencialmente establece que la verificación de tipo puede ser sofocante en algunos casos, e incluso si su programa pasa las comprobaciones de tipo, no es garantía de corrección: las pruebas adecuadas son la única manera de estar seguro.

Respuesta

7

Básicamente estoy de acuerdo con brian. La cantidad que debe preocuparse por las entradas de su método depende en gran medida de lo mucho que le preocupa que a) alguien ingrese datos incorrectos, yb) los datos incorrectos corrompen el propósito del método. También agregaría que hay una diferencia entre los métodos externos e internos. Debe ser más diligente con los métodos públicos porque está haciendo una promesa a los consumidores de su clase; por el contrario, puede ser menos diligente con respecto a los métodos internos, ya que tiene un mayor control (teórico) sobre el código que accede a él, y solo tiene usted la culpa si las cosas van mal.

MooseX :: :: Firmas Método es una solución elegante a la adición de una manera declarativa simple de explicar los parámetros de un método. Método :: Firmas :: Simple y Params :: Validate son agradables pero carecen de una de las características que me parecen más atractivas de Moose: el sistema Type. He usado MooseX :: Declare y, por extensión, MooseX :: Method :: Signatures para varios proyectos y creo que la barra para escribir cheques adicionales es tan mínima que es casi seductora.

13

Si es importante que compruebe que un argumento es exactamente lo que necesita, lo vale. El rendimiento solo importa cuando ya tienes un funcionamiento correcto. No importa qué tan rápido pueda obtener una respuesta incorrecta o un volcado del núcleo. :)

Ahora, eso suena como una estupidez, pero considere algunos casos donde no lo es. ¿De verdad me importa lo que hay en @_ aquí?

sub looks_like_a_number { $_[0] !~ /\D/ } 
sub is_a_dog   { eval { $_[0]->DOES('Dog') } } 

En estos dos ejemplos, si el argumento no es lo que usted espera, usted todavía va a obtener la respuesta correcta porque los argumentos no válidos no pasarán las pruebas. Algunas personas lo ven como feo, y puedo ver su punto, pero también creo que la alternativa es fea. ¿Quién gana?

Sin embargo, va a haber momentos en los que necesita condiciones de protección porque su situación no es tan simple. Lo siguiente que tiene que pasar sus datos es esperar que estén dentro de ciertos rangos o de ciertos tipos y que no fallen elegantemente.

Cuando pienso en las condiciones de la guardia, pienso en lo que podría pasar si las entradas son malas y cuánto me importa la falla. Tengo que juzgar eso por las exigencias de cada situación. Sé que apesta como una respuesta, pero me gusta más que un enfoque de esclavitud y disciplina donde tienes que pasar por todo el lío incluso cuando no importa.

Me da miedo Params::Validate porque su código suele ser más largo que mi subrutina. Las cosas de Moose son muy atractivas, pero debes darte cuenta de que es una forma de declarar lo que quieres y aún obtienes lo que puedes construir a mano (simplemente no tienes que verlo o hacerlo). Lo más importante que odio de Perl es la falta de firmas de métodos opcionales, y esa es una de las características más atractivas de Perl 6 y de Moose.

3

El argumento en contra que he visto presenta esto es que comprobar los parámetros en cada llamada a la función es redundante y una pérdida de tiempo de la CPU. Los partidarios de este argumento favorecen un modelo en el que todos los datos entrantes se verifican rigurosamente cuando ingresan al sistema por primera vez, pero los métodos internos no tienen verificaciones de parámetros porque solo deben llamarse por código que les pasará datos que ya pasaron las verificaciones en el sistema frontera, por lo que se supone que sigue siendo válido.

En teoría, me gusta mucho el sonido de eso, pero también puedo ver cuán fácilmente puede caer como un castillo de naipes si alguien usa el sistema (o el sistema necesita crecer para permitir su uso) de una manera que fue imprevisto cuando se establece el borde de validación inicial. Todo lo que se necesita es una llamada externa a una función interna y todas las apuestas están desactivadas.

En la práctica, estoy usando Moose en este momento y Moose realmente no le da la opción de eludir la validación en el nivel de atributo, más MooseX :: Declare maneja y valida los parámetros del método con menos complicaciones que desenrollar @_ a mano, por lo que es prácticamente un punto discutible.

+0

+1 for Moose info – DVK

1

Parámetros :: Validar funciona muy bien, pero por supuesto la comprobación args ralentiza las cosas. Las pruebas son obligatorias (al menos en el código que escribo).

2

Quiero mencionar dos puntos aquí. La primera son las pruebas, la segunda la pregunta de rendimiento.

1) Pruebas de

Usted ha mencionado que las pruebas pueden hacer mucho y que las pruebas son la única manera para asegurarse de que el código es correcto. En general, diría que esto es absolutamente correcto. Pero las pruebas en sí solo resuelven un problema.

Si escribe un módulo que tienen dos problemas o digamos dos diferentes la gente que utiliza su módulo.

Usted como desarrollador y un usuario que utiliza su módulo. Las pruebas ayudan con el primero que su módulo es correcto y que hace lo correcto, pero no ayudó al usuario que solo usa su módulo .

Para la tarde, tengo un ejemplo. había escrito un módulo usando Moose y algunas otras cosas, mi código terminaba siempre en un error de segmentación. Luego comencé a depurar mi código y buscar el problema. Gasto alrededor de 4 horas para encontrar el error.Al final, el problema fue que tengo que usé Moose con Array Trait. Utilicé la función "mapa" y no hice para proporcionar una función de subrutina, solo una cadena u otra cosa.

Claro que este fue un error absolutamente estúpido mío, pero pasé mucho tiempo en depurarlo. Al final solo una comprobación de la entrada de que el argumento es un subref le costaría al desarrollador 10 segundos de tiempo, y me costaría y probablemente otro mucho más tiempo.

También sé de otros ejemplos. Escribí un cliente REST a una interfaz completamente OOP con Moose. Al final siempre recuperas Objetos, puedes cambiar los atributos pero seguro que no llamó a la API REST para cada cambio que hiciste. En cambio, cambia sus valores y al final llama a un método update() que transfiere los datos y cambia los valores.

ahora tenía un usuario que escribió entonces:

 
$obj->update({ foo => 'bar' }) 

de que me dieron un error de vuelta, que se actualizan() no funciona. Pero seguro que no funcionó, porque el método update() no aceptó un hashref. Solo hace una sincronización del estado real del objeto con el servicio en línea . El código correcto sería.

 
$obj->foo('bar'); 
$obj->update(); 

Lo primero funciona porque nunca hice una comprobación de los argumentos. Y no arrojo un error si alguien da más argumentos entonces espero. El método simplemente comienza normal como.

 
sub update { 
    my ($self) = @_; 
    ... 
} 

Sure all my tests absolutamente funciona 100% bien. Pero manejar estos errores que no son errores me costó tiempo también. Y le cuesta mucho al usuario mucho de más tiempo.

Así que al final. Sí, las pruebas son la única forma correcta de garantizar que su código funcione correctamente. Pero eso no significa que la verificación de tipos no tenga sentido. La verificación de tipos está ahí para ayudar a todos los que no son desarrolladores (en su módulo) a usar su módulo correctamente. Y le ahorra tiempo a usted y otros al encontrar errores de volcado.

2) Rendimiento

El resumen: No te importa para un rendimiento hasta que importa.

Eso significa que hasta que su módulo funcione para ralentizar, el rendimiento siempre es rápido suficiente y no necesita preocuparse por esto. Si su módulo realmente funciona para reducir la velocidad, necesita más investigaciones. Pero para estas investigaciones debe usar un generador de perfiles como Devel :: NYTProf para ver qué es lento.

Y yo diría. En 99% de lentitud no es porque usted escriba comprobando, es más su algoritmo. Hace un montón de cálculos, llamando a funciones a menudo etc. A menudo ayuda si completa otras soluciones use otro mejor algoritmo, haga el almacenamiento en caché u otra cosa, y el golpe de rendimiento no es su tipo de comprobación. Pero incluso si la verificación es el golpe de rendimiento. Entonces solo quítalo donde sea importante.

No hay ninguna razón para dejar el tipo de comprobación donde no importa el rendimiento . ¿Crees que la verificación de tipos importa en un caso como el anterior? ¿Dónde escribí un cliente REST? El 99% de los problemas de rendimiento aquí son el importe de la solicitud que va al servicio web o el momento de dicha solicitud . No utilice la comprobación de tipo o MooseX :: Declare etc. propalablemente acelere absolutamente nada.

E incluso si ve desventajas de rendimiento. Algunas veces es aceptable. Porque la velocidad no importa o, a veces, algo le da un mayor valor de . DBIx :: Class es más lento que SQL puro con DBI, pero DBIx :: Class le ofrece mucho para estos.

4

Sí vale la pena - la programación defensiva es una de esas cosas que siempre valen la pena.

0

Estoy usando Moose extensivamente para un proyecto de OO bastante grande en el que estoy trabajando. La estricta verificación de tipos de Moose ha salvado mi tocino en algunas ocasiones. Lo que es más importante, ha ayudado a evitar situaciones en las que los valores de "undef" se pasen incorrectamente al método. Solo en esas instancias, me ahorró horas de depuración ...

El éxito en el rendimiento definitivamente existe, pero se puede gestionar. Dos horas de uso de NYTProfar me ayudaron a encontrar algunos atributos de Moose que estaba rebuscando demasiado y acabo de refactorizar mi código y obtuve una mejora de rendimiento de 4 veces.

Utilice la verificación de tipo. La codificación defensiva lo vale.

Patrick.

0

Sí, es absolutamente digno de él, ya que le ayudará durante el desarrollo, mantenimiento, depuración, etc.

Si un desarrollador envía accidentalmente los parámetros incorrectos a un método, se generará un mensaje de error útil, en lugar de la el error se propaga a otro lugar.

0

A veces. Generalmente lo hago cada vez que paso opciones a través de hash o hashref. En estos casos, es muy fácil olvidar o escribir mal el nombre de una opción, y consultar con Params::Check puede ahorrarle mucho tiempo de resolución de problemas.

Por ejemplo:

sub revise { 
    my ($file, $options) = @_; 

    my $tmpl = { 
     test_mode => { allow => [0,1], 'default' => 0 }, 
     verbosity => { allow => qw/^\d+$/, 'default' => 1 }, 
     force_update => { allow => [0,1], 'default' => 0 }, 
     required_fields => { 'default' => [] }, 
     create_backup => { allow => [0,1], 'default' => 1 }, 
    }; 

    my $args = check($tmpl, $options, 1) 
     or croak "Could not parse arguments: " . Params::Check::last_error(); 
    ... 
} 

Antes de añadir estas comprobaciones, me olvido si los nombres utilizan guiones o guiones, pasar require_backup en lugar de create_backup, etc. Y esto es para el código que escribí myself-- si otras personas van a usarlo, debes definitivamente hacer algún tipo de prueba de idiotez. Params::Check facilita bastante la verificación de tipos, la comprobación de valores permitidos, los valores predeterminados, las opciones requeridas, los valores de las opciones de almacenamiento para otras variables y más.