¿Hasta dónde llegar con un lenguaje fuertemente escrito?


Digamos que estoy escribiendo una API, y una de mis funciones toma un parámetro que representa un canal, y solo estará entre los valores 0 y 15. Podría escribirlo así:

void Func(unsigned char channel)
{
    if(channel < 0 || channel > 15)
    { // throw some exception }
    // do something
}

O aprovecho que C++ es un lenguaje fuertemente escrito, y me convierto en un tipo:

class CChannel
{
public:
    CChannel(unsigned char value) : m_Value(value)
    {
        if(channel < 0 || channel > 15)
        { // throw some exception }
    }
    operator unsigned char() { return m_Value; }
private:
    unsigned char m_Value;
}

Mi función ahora se convierte en esto:

void Func(const CChannel &channel)
{
    // No input checking required
    // do something
}

Pero ¿es esto un exceso total? Me gusta la auto-documentación y la garantía es lo que dice que es, pero ¿vale la pena pagar la construcción y destrucción de tal objeto, por no hablar de toda la mecanografía adicional? Por favor, háganme saber sus comentarios y alternativas.

Author: DanDan, 2010-07-05

14 answers

Si quieres este enfoque más simple generalizarlo para que pueda obtener más uso de él, en lugar de adaptarlo a una cosa específica. Entonces la pregunta no es "¿debería hacer una clase completamente nueva para esta cosa específica?"pero "debo usar mis utilidades?"; este último es siempre sí. Y las utilidades son siempre útiles.

Así que haz algo como:

template <typename T>
void check_range(const T& pX, const T& pMin, const T& pMax)
{
    if (pX < pMin || pX > pMax)
        throw std::out_of_range("check_range failed"); // or something else
}

Ahora ya tienes esta buena utilidad para comprobar rangos. Su código, incluso sin el tipo de canal, ya se puede hacer más limpio usándolo. Puedes ir más lejos:

template <typename T, T Min, T Max>
class ranged_value
{
public:
    typedef T value_type;

    static const value_type minimum = Min;
    static const value_type maximum = Max;

    ranged_value(const value_type& pValue = value_type()) :
    mValue(pValue)
    {
        check_range(mValue, minimum, maximum);
    }

    const value_type& value(void) const
    {
        return mValue;
    }

    // arguably dangerous
    operator const value_type&(void) const
    {
        return mValue;
    }

private:
    value_type mValue;
};

Ahora tienes una buena utilidad, y solo puedes hacer:

typedef ranged_value<unsigned char, 0, 15> channel;

void foo(const channel& pChannel);

Y es reutilizable en otros escenarios. Simplemente péguelo todo en un archivo "checked_ranges.hpp" y úselo siempre que lo necesite. Nunca es malo hacer abstracciones, y tener utilidades alrededor no es dañino.

Además, nunca se preocupe por los gastos generales. Crear una clase simplemente consiste en ejecutar el mismo código que haría de todos modos. Además, el código limpio es preferible a cualquier otra cosa; el rendimiento es una última preocupación. Una vez que haya terminado, puede obtener un generador de perfiles para medir (no adivinar) dónde están las partes lentas.

 60
Author: GManNickG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-04 04:13:31

Sí, la idea vale la pena, pero (IMO) escribir una clase completa y separada para cada rango de enteros no tiene sentido. Me he encontrado con suficientes situaciones que requieren enteros de rango limitado que he escrito una plantilla para el propósito:

template <class T, T lower, T upper>
class bounded { 
    T val;
    void assure_range(T v) {
        if ( v < lower || upper <= v)
            throw std::range_error("Value out of range");
    }
public:
    bounded &operator=(T v) { 
        assure_range(v);
        val = v;
        return *this;
    }

    bounded(T const &v=T()) {
        assure_range(v);
        val = v;
    }

    operator T() { return val; }
};

Usarlo sería algo como:

bounded<unsigned, 0, 16> channel;

Por supuesto, puede ser más elaborado que esto, pero este simple todavía maneja alrededor del 90% de las situaciones bastante bien.

 27
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-31 20:49:07

No, no es exagerado - siempre debe tratar de representar abstracciones como clases. Hay un millón de razones para hacer esto y la sobrecarga es mínima. Sin embargo, yo llamaría al Canal de la clase, no a CChannel.

 14
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 19:37:26

No puedo creer que nadie haya mencionado enum's hasta ahora. No le dará una protección a prueba de balas, pero aún mejor que un tipo de datos entero simple.

 11
Author: Seva Alekseyev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 19:51:02

Parece exagerado, especialmente el accessor operator unsigned char(). No estás encapsulando datos, estás haciendo las cosas evidentes más complicadas y, probablemente, más propensas a errores.

Los tipos de datos como su Channel suelen ser parte de algo más abstracto.

Entonces, si usas ese tipo en tu clase ChannelSwitcher, podrías usar el tipo comentado justo en el cuerpo ChannelSwitcher (y, probablemente, tu tipo será public).

// Currently used channel type
typedef unsigned char Channel;
 6
Author: Costantino Rupert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 19:34:28

Si lanza una excepción al construir su objeto "CChannel" o en la entrada del método que requiere la restricción hace poca diferencia. En cualquier caso, está haciendo aserciones de tiempo de ejecución, lo que significa que el sistema de tipos realmente no le está haciendo ningún bien, ¿verdad?

Si quieres saber hasta dónde puedes llegar con un lenguaje fuertemente escrito, la respuesta es "muy lejos, pero no con C++."El tipo de poder que se necesita para imponer estáticamente una restricción como," este el método solo se puede invocar con un número entre 0 y 15 " requiere algo llamado tipos dependientes--es decir, tipos que dependen de valores.

Para poner el concepto en una sintaxis pseudo-C++ (pretendiendo que C++ tenía tipos dependientes), puede escribir esto:

void Func(unsigned char channel, IsBetween<0, channel, 15> proof) {
    ...
}

Tenga en cuenta que IsBetweenestá parametrizado por valoresen lugar de tipos. Para llamar a esta función en su programa ahora, debe proporcionar al compilador el segundo argumento, proof, que debe tener el tipo IsBetween<0, channel, 15>. Es decir, tienes que demostrar en tiempo de compilación que channel está entre 0 y 15! Esta idea de tipos que representan proposiciones, cuyos valores son pruebas de esas proposiciones, se llama la Correspondencia Curry-Howard .

Por supuesto, probar tales cosas puede ser difícil. Dependiendo del dominio del problema, la relación costo/beneficio puede inclinarse fácilmente a favor de simplemente abofetear las comprobaciones de tiempo de ejecución en su código.

 6
Author: Tom Crockett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-30 10:43:57

Si algo es excesivo o no a menudo depende de muchos factores diferentes. Lo que puede ser exagerado en una situación puede no serlo en otra.

Este caso podría no ser excesivo si tuviera un montón de funciones diferentes que todos los canales aceptados y todos tenían que hacer la misma comprobación de rango. La clase Channel evitaría la duplicación de código, y también mejoraría la legibilidad de las funciones (al igual que nombrar la clase Channel en lugar de CChannel - Neil B. tiene razón).

A veces cuando el rango es lo suficientemente pequeño, en su lugar definiré una enumeración para la entrada.

 4
Author: SCFrench,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 19:45:41

Si agrega constantes para los 16 canales diferentes, y también un método estático que obtiene el canal para un valor dado (o arroja una excepción si está fuera de rango), esto puede funcionar sin ninguna sobrecarga adicional de creación de objetos por llamada al método.

Sin saber cómo se va a usar este código, es difícil decir si es excesivo o no o agradable de usar. Pruébelo usted mismo-escriba algunos casos de prueba utilizando ambos enfoques de una clase char y una clase typesafe - y vea cuál como. Si te cansas de él después de escribir algunos casos de prueba, entonces probablemente sea mejor evitarlo, pero si te parece que te gusta el enfoque, entonces podría ser un guardián.

Si esta es una API que va a ser utilizada por muchos, entonces tal vez abrirla a alguna revisión podría darle comentarios valiosos, ya que presumiblemente conocen el dominio de la API bastante bien.

 1
Author: mdma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 19:50:23

En mi opinión, no creo que lo que está proponiendo sea una gran sobrecarga, pero para mí, prefiero guardar la escritura y simplemente poner en la documentación que cualquier cosa fuera de 0..15 es undefined y usa una assert () en la función para detectar errores en compilaciones de depuración. No creo que la complejidad añadida ofrezca mucha más protección para los programadores que ya están acostumbrados a la programación en lenguaje C++, que contiene una gran cantidad de comportamientos indefinidos en sus especificaciones.

 1
Author: 5ound,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 20:31:04

Tienes que hacer una elección. No hay una bala de plata aquí.

Rendimiento

Desde la perspectiva del rendimiento, la sobrecarga no va a ser mucho, si es que lo es. (a menos que tengas que contar los ciclos de cpu), por lo que lo más probable es que este no sea el factor determinante.

Simplicidad / facilidad de uso, etc

Haga que la API sea simple y fácil de entender/aprender. Debe saber / decidir si numbers / enums / class sería más fácil para el usuario de la api

Capacidad de mantenimiento

  1. Si estás muy seguro del canal tipo va a ser un entero en en el futuro previsible, me iría sin la abstracción (considerar usando enumeraciones)

  2. Si tiene muchos casos de uso para un valores acotados, considere el uso de la plantillas (Jerry)

  3. Si piensas, el Canal puede potencialmente tienen métodos que sea un clase ahora mismo.

Esfuerzo de codificación Una vez cosa. Así que siempre piensa en el mantenimiento.

 1
Author: neal aise,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 21:03:32

El ejemplo del canal es difícil:

  • Al principio parece un tipo entero de rango limitado simple, como se encuentra en Pascal y Ada. C++ no te da forma de decir esto, pero una enumeración es suficiente.

  • Si miras más de cerca, ¿podría ser una de esas decisiones de diseño que probablemente cambiarán? ¿Podrías empezar a referirte a "canal" por frecuencia? Por cartas de llamada (WGBH, adelante)? Por la red?

Mucho depende de su plan. ¿Cuál es el objetivo principal de la API? ¿Cuál es el modelo de costos? ¿Se crearán canales con mucha frecuencia(sospecho que no)?

Para obtener una perspectiva ligeramente diferente, veamos el costo de meter la pata:

  • Expones el rep como int. Los clientes escriben mucho código, la interfaz se respeta o su biblioteca se detiene con un error de aserción. Crear canales es muy barato. Pero si necesitas cambiar la forma en que estás haciendo las cosas, pierdes " hacia atrás bug-compatibility " y molestar a los autores de clientes descuidados.

  • Mantenlo abstracto. Todo el mundo tiene que usar la abstracción (no tan mal), y todo el mundo está a prueba de futuros cambios en la API. Mantener la compatibilidad con versiones anteriores es pan comido. Pero crear canales es más costoso, y lo que es peor, la API tiene que indicar cuidadosamente cuándo es seguro destruir un canal y quién es responsable de la decisión y la destrucción. El peor escenario es que crear / destruir los canales conducen a una gran fuga de memoria u otro fallo de rendimiento, en cuyo caso se vuelve a la enumeración.

Soy un programador descuidado, y si fuera por mi propio trabajo, iría con la enumeración y me comería el costo si la decisión de diseño cambia en el futuro. Pero si esta API fuera a salir a muchos otros programadores como clientes, usaría la abstracción.


Evidentemente soy un relativista moral.

 1
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-06 03:19:55

Un entero con valores solo entre 0 y 15 es un entero de 4 bits sin signo (o medio byte, nibble. Me imagino que si esta lógica de conmutación de canal se implementaría en hardware, entonces el número de canal podría representarse como eso, un registro de 4 bits). Si C++ tuviera eso como tipo, se haría aquí mismo:

void Func(unsigned nibble channel)
{
    // do something
}

Por desgracia, no lo hace. Podría relajar la especificación de la API para expresar que el número de canal se da como un carácter sin signo, con el canal real siendo calculada usando una operación de módulo 16:

void Func(unsigned char channel)
{
    channel &= 0x0f; // truncate
    // do something
}

O bien, utilice un campo de bits:

#include <iostream>
struct Channel {
    // 4-bit unsigned field
    unsigned int n : 4;
};
void Func(Channel channel)
{
    // do something with channel.n
}
int main()
{
    Channel channel = {9};
    std::cout << "channel is" << channel.n << '\n';
    Func (channel); 
}

Este último podría ser menos eficiente.

 1
Author: madoki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-20 16:31:08

Voto por su primer enfoque, porque es más simple y más fácil de entender, mantener y extender, y porque es más probable que se mapee directamente a otros idiomas si su API tiene que ser reimplementada/traducida/portada/etc.

 0
Author: joe snyder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 20:48:38

Esto es abstracción mi amigo! Siempre es mejor trabajar con objetos

 0
Author: Saher Ahwal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-05 20:55:25