Cita:
Iniciado por dmorill Que es el mantenimiento de código? he buscado por google pero no encuentro nada especifico, supongo que tiene que ver con la métrica del código.
Después de realizar una aplicación toca el mantenimiento. Lo lógico es que las aplicaciones no estén exentas de fallos que toca corregir después del lanzamiento de la aplicación.
Además lo más normal es que una vez lanzada la primera versión se introduzcan nuevas funcionalidades y mejoras...
Uno de los factores que afecta de forma directa al coste que supone realizar estas tareas es el diseño de la aplicación. Un buen diseño reduce el coste de mantener el software en funcionamiento mientras que un mal diseño tiene el efecto contrario.
¿Cómo mejorar el índice de manteminiento? Suele haber varias directrices y de hecho verás que algunas son incluso antagónicas (lo que implica encontrar un punto de equilibrio):
- Reducir el número de líneas de cada función: Una función con 20 líneas suele ser más sencilla de revisar que una de 10.000 (y créeme que las hay)
- Reducir la profundidad de la herencia: La herencia suele crear acoplamiento (te obliga a arrastrar cosas que no necesitas)... éste es malo porque un cambio en tu código puede afectar a más áreas de la aplicación de las que piensas... mayor acoplamiento = más test a realizar.
- Reducir la cantidad de código: Hasta cierto punto suena lógico... cuantas menos líneas de código tenga una aplicación menos puntos de error va a tener.
- Tabular correctamente: Un código bien tabulado facilita enormemente su lectura lo cual supone una gran ventaja a la hora de lidiar con errores.
- Usar nombres coherentes: Si te encuentras una variable llamada `dd` ¿Cual es su utilidad? vete tu a saber. ¿Y una función llamada dd()? En línea con el punto anterior, usar nombres coherentes ayuda a entender el código lo cual te permite ahorrar minutos o incluso horas intentando descifrar el código.
- Evitar en la medida de lo posible el uso de "wildcards". Vale que todos podemos llegar a tener una obsesión enfermiza con el rendimiento, pero hay que saber ver que no es necesario optimizar al extremo todas y cada una de las funcionalidades de una aplicación. Hay algoritmos super optimizados para, por ejemplo este que tienen grandes dosis de ingeniería. Queda muy chulo encontrarlo y pensar "lo pongo en mi aplicación que esto mola"... después te encuentras con que tu programa no funciona como esperas y... ¿dónde está el error? Si se ponen cosas así es mejor dejarlas muy bien documentadas porque suele ser arriesgado tocar cualquier punto de ese código. Además, ¿tiene sentido que una acción provocada por el usuario tarde 1us en vez de 1ms? como norma general no si con ello se sacrifica la legibilidad del codigo de sobremanera.
- ... Podría poner puntos adicionales durante toda la mañana pero creo que con los expuestos queda clara mi postura :)
Cita:
Iniciado por dmorill Super, no sabía que existía este tipo de funciones. en el
find_if que lleva 3 argumentos, los dos primeros son iteradores y el tercero según
cplusplus es un UnaryPredicate (función que transforma el contendido de un elemento a booleano según entiendo) y tu pones un [I]
(const MyClass& item), lo que no entiendo es que significa ese "[i]" y entre paréntesis una clase que pasas por referencia. Los [] son operadores para entrar en ciertos contenedores tipo "v[3]" y luego con los paréntesis ya me volví loco jajaja
, pues creí que solo servían para llamar a funciones o para separar operaciones matemáticas largas.
Te presento un elemento que pasó a formar parte de C++ con la entrada en vigor del estándar C++11, las lambdas. Una lambda es una función creada al momento. Su uso es exactamente el mismo que el de cualquier función que podamos declarar de la forma tradicional. ¿Por qué usar entonces una lambda? A mi me gustan porque puedo poner el código en el mismo sitio donde voy a usarlo. ¿Y por qué no opto por usar entonces un bucle y me ahorro la lambda? Pues porque las funciones tipo std::find_if construyen el bucle por mí, lo que me permite centrarme en indicar qué es lo que quiero conseguir.
Como nota adicional yo generalmente uso las lambdas en chequeos sencillos de una o dos líneas para no complicar el código.
Un Ejemplo con una función al estilo tradicional y una lambda capaz de sustituirla:
Código C++:
Ver originalbool func(int valor)
{ return valor==2; }
int main()
{
std::vector<int> valores{1,2,3,4,5,6,7,8};
// Sin lambda
auto it = std::find_if(valores.begin(),valores.end(),func);
// Con lambda
auto it = std::find_if(valores.begin(),valores.end(),
[](int valor){ return valor == 2; });
std::cout << *it;
}
En cuanto a tu pregunta sobre el formato de las lambdas te comento. La estructura general de una lambda es [1](2) -> 3 {4}, donde:
- Entre los corchetes se indican las variables que queremos usar dentro de la lambda. Estas variables se pueden pasar como referencia, lo que permite a la lambda modificar su valor. Si los corchetes están vacios entonces la lambda no puede acceder a ninguna variable de nuestra función. Esta sección admite varios usos:
- [] La lambda no tiene acceso a variables externas.
- [&] La lambda tiene acceso a todas las variables declaradas en la función y además cualquier cambio en dichas variables se reflejará fuera de la lambda
- [=] Una copia de todas las variables externas está disponible dentro de la lambda.
- [=, &var] Se copian todas las variables menos var que se pasa por referencia.
- [var] Únicamente estará disponible una copia de la variable var
- [var,&var2] Disponemos de una copia de var y var2 es accesible por referencia.
- [this] Se copia el puntero de la clase actual.
- Los paréntesis delimitan los argumentos que va a recibir la lambda... como en el caso de cualquier otra función.
- Este punto puede ser opcional y permite indicar el valor de retorno de la lambda. En funciones de una sola línea el compilador es capaz de identificar este valor por lo que podemos ignorar este punto.
- El código de la lambda... como si fuese una función más
El puntero a una lambda se puede almacenar en una variable... para ello lo ideal es usar
auto o
std::function:
Código C++:
Ver original#include <iostream>
#include <functional>
int main()
{
// Las dos definiciones son equivalentes porque el compilador es capaz de deducir el tipo de retorno de la lambda
auto func = [](int valor) {std::cout << valor << std::endl; };
auto func = [](int valor) -> void {std::cout << valor << std::endl; };
func(3);
// también se puede almacenar en std::function
std::function<void(int)> func2 = func;
func2(5);
}
Cita:
Iniciado por dmorill Hay algunos ejemplos en internet que mencionan más o menos los diferentes usos de explicit, podrías explicarme un poco más qué es y cuando se puede usar?
explicit es un modificador que se pone en los constructores (y en los operadores de conversión para C++11 en adelante). Al añadir esta palabra a un constructor evitamos que se realicen conversiones implícitas. Por ejemplo:
Código C++:
Ver originalstruct Foo
{
Foo(int v)
: valor(v)
{ }
int valor;
};
struct Foo2
{
explicit Foo2(int v)
: valor(v)
{ }
int valor;
};
void func(Foo foo)
{ std::cout << foo.valor << std::endl; }
void func2(Foo2 foo)
{ std::cout << foo.valor << std::endl; }
int main()
{
// Compila. Se llama de forma implícita a Foo(int)
func(1);
// No compila. La llamada a Foo2(int) ha de indicarse de forma explícita.
func2(1);
// Compila
func2(Foo2(1));
}
Esta palabra evita que se realicen determinadas operaciones de forma colateral. Nos obliga a indicar de forma explícita las conversiones lo que hace que seamos un poco más conscientes de qué es lo que realmente se está ejecutando.
Un saludo.