jueves, 14 de septiembre de 2023

Management-level bugs fixed at the development level.

Management-level bugs fixed at the development level.

The following lines are solely my opinion, and I'm certain that I could be mistaken.
The next lines are likely to be familiar to all of us, but I believe it's necessary to emphasize them.

I believe that most of the time, it is assumed that bugs only exist at the development level. This assumption stems from the fact that when something is wrong with the software, who has developed it? Yes, the software developer, programmer, software engineer... So, who is going to shoulder the blame? Can you guess? ;-)

So, at what other levels can bugs exist? For example, at the management level, human resources level, and so on. At these levels, a bug can be more costly than at the development level. Even a bug at the management level can result in several software bugs. I will attempt to provide some examples. However, these management-level bugs will not be visible to the customer or the final user of the software; They will only experience the software bugs, poor quality, lack of user-friendliness, and so on,..





Let's go to see same examples.



"Recruitment Policy": If management does not take care of this and does not pay attention to finding the best profile that can fit better into their project, it could lead to more software bugs. If during the job interview, you only ask, "Do you know C++? - Yes - Okay, perfect, you're hired," and ask only a few superficial questions, it doesn't predict well for your project. Why? Because maybe the new developer knows C++, but finds it challenging to read source code written by someone else. They might also introduce anti-patterns that make the app more error-prone and less maintainable, ultimately resulting in more bugs. On the other hand, for developers, if you're in a job interview and are only asked if you know C++, it can give you some insight into whether they truly care about software developers.

"Wrong Technology": If, for some reason, management has decided to use a particular technology, the programmers will not only struggle with the project's inherent complexity (essencial complexity) but also with the chosen technology itself (accidental complexity), making the development process even more painful. If the wrong technology choice complicates the project, there will likely be more errors and a higher chance of project delays.

"Poor Environment": The environment encompasses everything surrounding the project. This includes the personal computer you use, whether the company provides excellent restaurant services, whether you have access to the right development tools, and whether your manager is open to providing new software development tools. These factors can make a significant difference. Additionally, a psychologically safe environment where you can freely express your opinions without fear is crucial. If the environment is not conducive, developers may feel that management doesn't care about them, which can affect their dedication to the software they are developing (performing regression tests more efficiently, generating new ideas to enhance software quality, etc.).

"Polling Management": This type of management involves constantly asking, "When will the functionality X be finished?" week after week, but it doesn't involve asking if you're facing any problems or if they can offer assistance. This management approach can create a negative office environment. "Polling management" can be done even by my mother ("Have you tidied up the room?", "Have you tidied up the room?", "Have you tidied up the room?"), but the challenging part is attempting to help developers meet their deadlines. The latter approach is more beneficial.

"Misunderstood requirements": Most of the requirements are gathered by the management, and an error in this phase can result in significant bugs. This is because, based on these requirements, the software architect must design an initial draft of the software architecture. If the requirements are incorrect, you might end up selecting the wrong architecture. Changing the architecture of an application can be quite expensive.

So what happen when the above happen? in my point of view, these management bugs are trying to solve at development level. consequence? death march, burnout, stress, unhappy, high rotation,...So I believe that each level has own bugs, and each level must to learn, think about, improve each day in order to reduce them.

Indeed, there will likely be more management-level bugs, but the crucial point is that these types of bugs are indirectly visible to the customer through the multiplication of issues within the software application. This can result in numerous bugs, significant delays, and poor quality."

If you are a manager, perhaps this can offer you another perspective. It's also evident that I may not have an in-depth understanding of the management role. However, I believe that if we all have a better understanding of each other's roles, we can collaborate more effectively. And if you are a developer, you may come to realize that it's not always our fault. When a software system encounters issues, responsibility should be shared among ALL team members and individuals involved in the project.

I hope this sheds some light on the fact that bugs can occur not only at the development level.

Best and happy design&coding.

felix.romo.sanchezseco@gmail.com

viernes, 28 de abril de 2023

Anatomy of a technical debt: for managers.



Firstly, I understand that most managers are familiar with the concept of 'technical debt'. However, it seems to me (and I believe many software developers would agree) that technical debt is not always given the priority it deserves by managers.

Managers often view the code and technical aspects of a project as a black box, simply asking whether it works or not and whether the client is complaining about it. These are the ways that, in my opinion, managers tend to approach the technical side of a software project. On the other hand, since they are managers, it's true that they are not required to perform technical tasks.




So, why should managers care about technical debt? Because technical debt can affect the project's cost, competitiveness, and profitability, and managing these aspects is already part of the manager's job. 

For this reason, I believe that managers should take care of technical debt and help to reduce it. This way, all team members can work to reduce technical debt according to their roles; 

 - architect/technical leader: constructing a good design
- developers: coding understandable code
 - managers: Creating a job framework that helps architects/technical leaders reduce technical debt.

To see this better, I will try to show it with a concrete example. I am sure that there will be better than this, but I will try my best.

Let's imagine that we are developing fire simulation software.

The very first version does the minimum: it simulates the spread of fire in a specific region, and the client can observe how the fire spreads across the cartographic map in our software.

Step 1: The users are happy as they can simulate fires and observe their evolution through the cartographic map.

Step 2: The client wants to see other types of maps, such as a digital model terrain, JPG images that represent the type of vegetation, and so on.

So, the technical leader realizes that this kind of functionality can conceal other issues and wants to visit the client and users to gain a better understanding of the new feature.

Smell 1: In the end, the technical leader is told that the feature is very simple - "it's just a matter of switching between maps". At this point, I believe that the technical lead and senior developers should visit the client and users to gain a better understanding of these new features. So managers should help facilitate communication between the development team and users. In this way, manager can help reduce tecnnical debt.

Step 2: The client wants this new feature as soon as possible, so the development team works to implement it quickly. They add a new variable, 'int m_iLayer,' to the base class CGisObject (CRaster, CPoint, CPolyline, ...). So, in this way, depending on which layer is selected in the application, the draw method will draw the CGisObjects that are equal to m_iLayer.

Technical debt 1: There may be a loss of performance in the application at this point, as the loop will iterate through all the GIS objects, even though some may not need to be drawn. And it will execute according with the numbers of layers the user is using.

For 1 to N actives Layers

begin
Foreach gisobject in ListObjects
   if ( gisobject.iLayer pertece a capas seleccionas por el usuario)
draw(gisobject)
end.
end

Step 3: Users who are using the new version have noticed that with a large amount of data points, polylines, etc., the performance is getting worse.

Now, users can switch between layers to observe fire evolution, but they realize that if they switch to another layer, they lose the previous information. So, they approach us with a new functionality.

"Transparency can be applied to layers". With this functionality, users will be able to see the evolution of the fire, the type of vegetation, and the digital terrain model at the same time, for example. So, in one view, they can analyze the influence of the terrain on the spread of fire, as well as the type of vegetation present.

Smell 2: at this point, if we choose to go fast and delivery this new feature with current design, it is going to be a mess in the future. We will be creating such a large technical debt that adding new features will become a nightmare, and it will be even more difficult for new developers to understand the code. Why? becouse a new concept of problem domain has came up, Layer, and we have not taken into account in our current design, and now, with this new feature of the transperency, we can not longer ignore the concept of Layer in the domain problem. We must do a refactoring.

What can we do? The technical leader should be honest with the manager and communicate that they need time to refactor the application to introduce the new concept of the problem domain, Layer. And the manager will have some options:

- Option A: Pushing the team to deliver the feature as fast as possible. The worst scenario. Most developers will recognize the management policy in place within the group, and they will start the 'death march'. So the manager will end up with software that has a huge technical debt, and new developers will struggle to maintain it, leading to the addition of even more technical debt as they struggle to understand the design. So, the project cost will increase because the team will spend more time maintaining the software and adding new features due to the technical debt incurred, ultimately resulting in a less efficient and more expensive project. The clients/users will be unhappy because the quality of the software is poor, resulting in performance issues, a bad user experience, and numerous bugs.

- Option B: Provide the necessary job framework, including time, resources, and development environment, for the team to tackle the technical debt and carry out the required refactoring. In this way, the refactored design can be implemented in a better and easier way to add new features. The team will see that the manager supports them, and together, they can help to keep the technical debt at a minimum possible level.

- Options 1...N: Other options that I would not know.

And how can we explain why option A is chosen most of the time? The explanation can be found in the concept of 'Hyperbolic discounting'. In simple words, "Hyperbolic discounting" means that humans prefer sooner rewards with less benefits than later rewards with higher benefits.

In the given example, if there had been a communication framework in place between the client, users, technical leader, and developers, the feature of 'transparency on layers' would have been discussed and identified in the first meeting itself.

With the result being significant cost savings, not only in terms of money but also in reducing technical debt, as the software team would have taken the concept of Layer into account from the beginning.

In conclusion, as we have seen above, ignoring technical debt can lead to an increase in project costs, loss of competitiveness, and unhappy clients/users - all of which are detrimental to business. These factors should matter to managers, who should take into account technical debt and help their teams cope with it. Technical debt is not solely a concept that should be managed by technical leaders and software developers but also by managers.

How can managers help reduce technical debt? In my opinion, as a software developer:

- Create a job framework where honest communication exists among all members of the team, including managers, technical leaders, developers, users, stakeholders, and so on.

- Managers should have knowledge about the basic technical vocabulary of the project (technology is change at fast speed, even, catching up for developers it is difficult) . So when the technical leader tells them, "We have a huge technical debt," "We must do regression tests," "To better understand the codebase, it would be helpful to pair a senior developer with a junior for a while," "We need better computers," "We need static and dynamic code analysis tools to detect static and runtime errors," "We have to consider upgrading to the latest, more stable development tools," etc., they will have an understanding of what is happening in the engine room and can take appropriate action to help reduce technical debt. Therefore, we will have a better chance of developing great software that can easily compete with other similar software.

I hope (in what I have been able to) this post has helped to provide a better understanding  of this problem from both sides of software development: management and technical perspectives.

Felix

felix.romo.sanchezseco@gmail.com

lunes, 10 de abril de 2023

Clean Desk Policy, really necessary? How expensive is it?

Clean Desk Policy, really necessary? How expensive is it?

In this post, I would like to put my point of view about the clean desk policy. 

I know that it is a new trend and, like any new trend, it seems to be good in all of its aspects, but I am not a big fan of it.

I will write my arguments which may seem weak or without basis to you. So it is only my opinion. And as an opinion, it could be wrong.


It is seen that the clean desk policy was born with the 5S. The 5S stands for:

Sort, Set, Shine, Standarize and Sustain. (1)

Basically, the purpose of this policy is to maintain a clean desk, make tools and documentation easy to find, and protect critical documentation.

And can these principles, which fit well in just-in-time manufacturing, also be applied effectively in software development?

In some way, when these principles were applied to software development, the first thing to do was: no one would have their own desk.

So if no one has his own desk, everyone will have to clean the desk before leaving.And with this action, one can almost fulfill the 5S. 

It is said that a clean desk promotes a professional image, protects critical documents, and encourages collaboration with other coworkers.

- professional image: I believe that a professional image must be primarily achieved through satisfying customers. But all employee can clean his own desk before leaving.

- protect critical documents: each employee should have desk drawers with locks to store critical documents.

- collaboration with other coworkers: I believe that one should be close to the coworkers with whom they need to communicate more often. If I work on system A, which communicates with system B, I would be very keen to sit near my coworkers who work on system B.


So, if professional image, document protection, and collaboration can be achieved without the "clean desk policy," why is it so popular?

I believe that it is mostly to save costs. It is more expensive to have 100 desks than 50, isn't it?. Companies have to look for ways to reduce costs. And I understand this and they should do it.


And one way of doing it is by implementing a "clean desk policy". We could call this "hot desking" (2). But..But what about the cost of implementing the 'clean desk' policy? How would it affect the process of software development? 

Do you think that the "clean desk" reduces the cost of the software development process? or does it increase? In my opinion, implementing a clean desk policy may increase costs, but further studies would be needed to confirm this.

When a person owns a desk, they will typically sit near other people with whom they talk more often. In other words, the people who make up a team will be sitting near each other. In this way, the information about the software development can be transmitted more easily. And here is where the key is. The better the information is transmitted among the team members, the lower the probability of misunderstandings, the better the understanding about the work, and the less time required to resolve problems. Saying that, it seems to me that information is the blood of software development. What would happen if our brain, our neurons worked with a 'clean desk policy'?. One group of cortex neurons in the foot and another group of cortex neurons in the hand. How efficient would their communication be?. Therefore, if each member of the team is placed in a random desk away from the rest of the team, how efficient would their communication be?. So, what would be the cost of having inefficient communication in a software project?. Hard to calculate but not cheap.

In the other hand, when you have your own desk, you can set it up in a way that makes you more productive.

In conclusion, I believe that if a software company wants to implement the clean desk policy, it should follow these points:

1. Assign clean desk zones divided by teams, so that each software team has their own designated clean desk zone. This way, only team members will be allowed to do "clean desking" in their designated zone.

2. Create the "clean desk zones" based on the communication patterns among teams. If Team A communicates frequently with Team B, we should create a "clean zone A" near "clean zone B"."

But I must say that I prefer each developer having their own desk.

felix.romo.sanchezseco@gmail.com

Refs:

(1): https://en.wikipedia.org/wiki/5S_(methodology)

(2): https://en.wikipedia.org/wiki/Hot_racking


jueves, 17 de noviembre de 2016

Network Virtual Enviroment with Flocks

Network Virtual Enviroment with Flocks


Introducción

Hace unos años me intereso el algoritmo que podría simular los movimientos de los pájaros, de grupos de pajaros. Esto es lo que se conoce como flocking algorithm

Estos algoritmos, pueden simular no solamente movimientos de pájaros, si no lo que sería de un grupo de objetos, individuos,...en este caso, yo elegí tanques, pero la implementación es general, no es siguiendo exactamente lo que haría un grupo de tanques.





Pero ahora, que hace pocos meses me he metido, por mi nuevo trabajo, en temas de simulación, donde un ordenador realiza una tarea, y se la comunica a otra por red,...y así, crear un simulador, y de hay lo que sería un Network Virtual Enviroment. Y entonces, quería aprender un poco más sobre esto, y que mejor que crear tu propio NVE.

NVE de Tanques

Y que mejor que para comprender mejor este mundo, que leer un buen libro. Y me leí el libro

"Networked Graphics Building Networked Games and Virtual Environments" de Anthony Steed, Manuel Fradinho Oliveira

Y vaya casualidad, que esos, usaron el movimiento de flocking para simular un NVE. Es decir, tener un flock (o grupo de objetos que deben moverse) en un ordenador y que los demás vean a ese grupo. Y asi crear un NVE.

Y entonces, me dije, de modificar el que ya había desarrollado. Abajo un pantallazo. Siento haberlo realizo con solo dos Flocks clientes, pero no me daba la memoria para tener dos maquinas virtuales corriendo :-( . Tengo que poner megas pero ya...



Arquitectura del NVE de Tanques

En el libro, se detallan varios tipos de arquitecturas, pero la que más me gusto implementar fue
la de peer to peer con servidor de entrada. Esta arquitectura, hay dos aplicaciciones, una el cliente que simula el flock y el servidor (otra aplicacion). El cliente solo debe saber la IP de donde está el servidor, y el servidor, le enviará las IPs de los demás clientes, y asi de esa forma, la configuración es un tanto más cómoda.






Aquí, los Flocks Client, al iniciar la ejecución se comunicaran con el servidor (en naranja) para que le envíe la lista de las ips de los otros ordenadores donde se simula y así entrar en el NVE. Una vez que tiene la lista, ya no se comunica más con el servidor y ya lo hace peer to peer con los demás ordenadores que están simulando los flocks.

Los pasos que se siguen a nivel general, son los siguientes en el Flock cliente cuando se esta simulando.

paso 1
- Actualizar el Flock Local (pero teniendo en cuenta los flocks remotos)
- Enviar la información de Flock Local a los demás flocks remotos.

Por otro lado, ¿como el cliente flock obtiene los flocks remotos?

Paso 2

Se define una clase RemoteSimulations, que representa a los flocks remotos, y internamente, está a la escucha de recibir información de los flocks remotos (por un puerto UDP ya predefinido entre los client flock), y cada vez que recibe uno, lo añade a la lista, que esta lista, la pedirá el paso 1, cuando el algoritmo de Flocking tenga que tomar en cuenta todos los flocks (local y remotos) para realizar el calculo del movimiento.

Y aquí dejo un video del NVE de tanques....


Futuro

En el libro anteriormente citado, se comenta los problemas que hay en los NVE, que surgen por la latencia de la red. Como que de repente un objeto se mueve más rápido, o parece que se para, o que vaya a tirones. Sería interesante simular estos problemas para así, poder desarrollar medidas que palíen en cierto grado estos problemas.


Félix Romo
felix.romo.sanchezseco@gmail.com





martes, 13 de octubre de 2015

Patrón de Diseño - Decorator


Introducción


Los patrones de diseño es algo que leí hace tiempo, he usado algunos en los proyectos en los que trabaje y en los que trabajo, pero una forma de asimilarlos, creo yo, es practicar con ellos con ejemplos y por eso este post. Con este post,intentaré implementar un patrón de diseño pero de forma que fuese como parte de algo real. Digo esto por que hay algunos libros en los que ponen los ejemplos con nombres de clases como "classA" "parrentClassA". Aunque puede ser explicativo, a mí se me queda mejor con nombres más concretos.

Y como hay mucha, mucha información, donde explican este tipo de patrón, dejo más que nada, la fuente original.

"Design Patterns: Elements of Reusable Object-Oriented Software"




Hace tiempo estuve trabajando en una empresa en la que por pantalla visualizaba viajes de buses, y pasando el cursor, mostraba informacón sobre el bus.

He tomado este caso para hacer un ejemplo del patrón Decorator.

Patrón Decorator


En este ejemplo, tenemos unos autobuses (Bus.cs) los cuales los conduce un conductor asociado al Bus(DriverInfo.cs). Y unos de los requisitos es que cuando pasa el cursor por el bus dibujado en pantalla, que se muestre la informacón asociada al conductor. En este caso, para complicar un poco más la cosa, se muestra dos informaciones del conductor:
- Información del nombre del conductor (Texto) (DecoratorInfoDriver.cs)
- Gráfica de las horas conducidas (Gráfico) (DecoratorGraphicsDriver.cs)

Aquí, abajo se muestra el diagrama de clases.


Comentarios


En el patrón de diseño decorator, se ve que los objetos que decoran el objeto, tiene el mismo interfaz o métodos que
el objeto que decora, para que de esta forma, los objetos clientes, no 'noten' la diferencia y traten a ambos como iguales.

Pero luego, como los objetos se les añade funcionalidad a través de añadir decorator, pero ¿y a la hora de quitarla?. A la hora de quitarla, sería, supongo, quitar los objetos decoradores, pero en este ejemplo, se quitan todos los decorados a la vez. La forma en que se quita un objeto un decorator es de la forma.

//borrar de la lista de _buses todos los objetos decorators (los que muestran la información del conductor)
private void removeInfoDriverInfo()
{
    for (int i = 0; i < _buses.Count; i++)
    {
        DecoratorInfo decoInfo = _buses[i] as DecoratorInfo;

        //Si hay en la lista algún objeto decorado..
        if (decoInfo != null)
        {
            //obtener el objeto que esta siendo decorado
            Bus bus = decoInfo.GetVehicule() as Bus;
            _buses.Remove(decoInfo); //borrar el objeto decorado de la lista de visualizaicon
            _buses.Add(bus); //añadir a la lista de visualiacion el objeto que era decorado
        }
    }
}

El método removeInfoDriverInfo, en runtime, va añadiendo y borrando objetos a la lista de visualización y de está forma, se añade o se borran las funcionalidades de cada objeto.

Luego el método "GetVehicule", retorna le objeto que esta siendo decorado. No estoy seguro, si me estoy ciñendo a la teoría, pero al acceder al objeto que esta siendo decorado, era necesario, para incluirlo dentro de la lista de visualización (List<ControlsUI.VehicleUI> _buses), y una forma de obtener el objeto decorado através de sus decoradores era con el método GetVehicule() (también se podría haber llamado GetDecoratedObject(), pero como estamos dentro del dominio del problema de los buses...)

public VehicleUI GetVehicule()
{
    if (_vehicle is DecoratorInfo)
    {
        DecoratorInfo decoratorInfo = (DecoratorInfo)_vehicle;
        return decoratorInfo.GetVehicule();
    }
    else
    {
        return _vehicle;
    }
}


Conclusión

La conclusión, una que por lo menos saco, es que si no lo hubise implentado, no me hubiese encontrado con el problema de como recuperar el objeto decorado, para quitar de esta forma, la funcionalidad añadida que le dan los objetos decoradores.

Habrá muchas formas, pero la que he escogido para simplificar, es que en a lista  List<ControlsUI.VehicleUI> _buses, estén todos los objetos, tanto Bus como  ecoratorGraphicsDriver y/o DecoratorInfoDriver, si se van borrando y añadiendo objetos a esta lista, a la par que quiera el usurio que un Bus tenga una funcionalidad añadida (ver info del conductor) o no.

Y creo que lo mismo ha sido un ejemplo, muy sencillo, pero ha sido una forma de verlo un poco más, y de no solo leerlo.

El código fuente se puede descargar aquí.

Félix Romo
felix.romo.sanchezseco@gmail.com



miércoles, 9 de septiembre de 2015

Contar Olivos con Ortofoto: Parte II (Clustering con K-Means)


Introducción


El siguiente post, por su propio titulo lo indica, es la segunda parte del post 'Contar Olivos con Ortofoto: Parte I'.

En esta segunda parte, se trata de ver que métodos o de que forma se puede discriminar, que son olivos y que no son, para de esta forma tener el número de olivos de una ortofoto con el menor margen de error (+- 4 olivos creo que es un margen razonable¿?):

Nota: para quién quiera ver los olivos que tiene cada recinto, una herramienta que puede utilizar es el SIGPAC (Sistema de Información Geográfica de Identificación de Parcelas Agrícolas).



Redes Neuronales

Utilice redes neuronales con una capa oculta, pero no era capaz de aprender con un margen de error mínimo.

Como un olivo puede estar representado por diferentes tamaños en pixeles. Por ejemplo, 20x20, 15x15, 4x4,..etc..lo que se hacía, era pasar todas las imagenes a un tamaño fijo, 7x7. Con este tamaño, las muestras de olivos, y las muestras de no olivos, para que aprendiese la red neuronal, daba un error grande, y no aprendía bien. Era por que la diferencia entre las imagenes de los olivos y los que no eran olivos, era muy pequeña.

Es posible, con ortofotos de alta calidad, donde un olivo puede ser representado por tamaños de 50x50, se pueda usar una red neuronal.¿?. Ya que la diferencia ahora, entre una imagen de lo que es un olivo y lo que no, se diferencian más.

Entonces, ¿qué usar?


Clustering k-means

Para una mejor explicación, ver Clustering k-means.  Y a mi modo de ver, este algoritmo lo que hace es que dado un número determinado a priori de clusters, el algoritmo, va mirando si los valores de un elemento, un objeto, está más cerca de un cluster o de otro. Y encuentra un cluster que este más cerca, asigna ese objeto a ese cluster. Creo que no está muy bien explicado. En el caso concreto que nos atañe.

Se han identificado por ejemplo 100 posibles olivos de diferentes tamaños (5x5, 20x20,..etc). Ahora, el algoritmo, inicializa aleatoriamente, los clusters a los que pertenece cada objeto. Es decir, va diciendo..."objeto 1 tu perteneces aleatoriamente al cluster 0, tu el objeto 2, al cluster 3,...hasta 6 clusters".

Luego se calcula el centroide de cada cluster. El centroido sera el tamaño medio de los objetos que lo componen. 
Y luego ya por ultimo, entramos en el 'while'. Que será, mientras haya cambio de un objeto de un cluster a otro, se hace: se va por cada objeto, y se mide la distancia (distancia de Euler) de ese objeto a todos los cluster, si se encuentra que el cluster más cercano encontrado, es distinto al que pertenece el objeto, eureka!, el objeto no pertenece a ese cluster y se pasa al otro.

Al final del algoritmo, en este ejemplo, tenemos 6 clusters, y cada uno con objetos similares entre sí.

Aquí, se supone que como los olivos serán casi todos del mismo tamaño, estarán casi todos en un mismo cluster, y los objetos que no son olivos, en otro clusters distinto.

Veamos un pantallazo.



Ene el pantallazo se ven rectangulos semitransparentes. Esto son cluster que se han determinado que no entran a formar parte del computo de la suma de los olivos. Y como se van son rectangulos que no tienen nada que ver con un olivo.

Pero, aquí está el problema que veo, que hay objetos en un cluster, que son olivos y otros que no son. Por ejemplo, el Cluster 5, son olivos, pero luego hay etiquetas 5 en el tejado de una casa.

¿como solucionar esto?

Un solución rápida, sería que el usuario, definiera una región de la ortofoto, donde se le diga, donde debe contar, y los objetos que estén fuera de esta región (la marcada por el trazado rojo), no entrar en el conteo de los olivos. 

El trazado rojo, está dibujado con el Paint, y no ha sido implementado en el programa, pero no sería un ejercicio complicado el crear una región y aplicar clipping.

Conclusión

No ha sido muy elegante la solución, por el hecho de tener que dibujar una región, pero si por lo menos para una primera versión, si contaría los olivos correctamente. Ahorrando los costes de llevar personal para que los cuente (tiempo, viajes, dietas,....).

El programa en release está aquí.

Félix Romo
felix.romo.sanchezseco@gmail.com





martes, 1 de septiembre de 2015

Un producto software, varios clientes. O como customizar.

Introducción


Hace un tiempo, unos años, trabaje en una empresa donde tenían un producto para varios clientes, Y cada cliente, requería un nuevo requisito, o quitar alguno, esto generaba código del estilo:

#if __CLIENT_A__

// habilitar caracteristica del cliente A


#endif


Este tipo de código a lo largo, de miles de lineas de código, mezclado con las demás funcionalidades de los demás clientes, el código es más difícil de seguir que una demostración matemática (al menos para mí...)

  Hace un tiempo, me tope con este problema, pero no quería extender la complejidad de los #if a lo largo de todas las clases del programa. Debía de encapsular esta complejidad en una clase.

Pero antes, veamos un ejemplo práctico.

Ejemplo práctico


Imaginemos, que tenemos un software que tiene las siguientes funcionalidades:

- Exportar sus datos a un fichero Txt (__TXT__EXPORT__ )
- Exportar sus datos a un fichero Excel (__EXCEL_EXPORT__)
- Visualización 2d (__2D_VIEW__ )
- Visualización 3d (__3D_VIEW__ )
- Exportación del modelo 3d generado a un fichero (__EXPORT_3D_MODEL__ ). Esta funcionalidad solo se activo si está activa la visualicación 2d y 3d.

La función drawOptionsInstallOldWay dibuja en pantalla las opciones instaladas de la forma #if. Y la función drawOptionsInstallNewWay las dibuja de otra forma que ya veremos.

El aspecto de la aplicación sería con las siguientes opciones activadas:
__TXT__EXPORT__, __2D_VIEW__,__3D_VIEW__





La parte izquierda se dibuja con el método drawOptionsInstallOldWay y la parte derecha (azul) con el método drawOptionsInstallNewWay .

Ahora, veamos cual de los dos métodos es más difícil de leer y de mantener a la larga.

 private void drawOptionsInstallOldWay(Graphics g)
        {
            
            float y = 40.0f;
#if __TXT__EXPORT__ 
                g.DrawString("Txt Export", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __EXCEL_EXPORT__
                g.DrawString("Excel Export", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __2D_VIEW__
                g.DrawString("2d View", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __3D_VIEW__
                g.DrawString("3d View", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __3D_VIEW__ && __2D_VIEW__ // __EXPORT_3D_MODEL__
                g.DrawString("Export 3d Model", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif


        }


 private void drawOptionsInstallNewWay(Graphics g)
        {
            float y = 40.0f;

if(RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_TXT__REQUERIMENT))
            {
                g.DrawString("Txt Export", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }
if(RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT))
            {
                g.DrawString("Excel Export", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }

            if(RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_2D_REQUERIMENT))
            {
                g.DrawString("2d View", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }

            if (RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_3D_REQUERIMENT))
            {
                g.DrawString("3d View", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }

            if (RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GENERATE_3D_MODEL))
            {
                g.DrawString("Export 3d Model", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }
        }

Vemos que el método drawOptionsInstallNewWay no hace uso de #if, sino de llamadas a un objeto de tipo RequerimentsActivator, el cual, le responde si una funcionalidad o requisito está activo o no.
Con esto se consigue que la complejidad de los #if, está centralidad, encapsulada en la clase RequerimentActivator.

La clase RequerimentActivator


Esta clase sigue el patrón de diseño singleton. De esta forma, se podrá acceder desde cualquier parte del programa, pero, yo creo que las partes donde se debería de acceder, es desde el interfaz, pero no llamarla desde las clases del problema de la aplicación.
Es decir, hacer uso de esta clase, RequerimentActivator, solo desde el MainForm.cs o clases que deriven de la clase Form. Así, las clases del dominio del problema no estarán acopladas a esta clase.


Esta clase tiene un enumerado llamado OptionalRequeriments donde se enumeraran todas las opciones que se pueden activar y desactivar.

public enum OptionalRequeriments { EXPORT_EXCEL__REQUERIMENT = 0, EXPORT_TXT__REQUERIMENT = 1, GRAPHICS_2D_REQUERIMENT = 2,GRAPHICS_3D_REQUERIMENT = 3, GENERATE_3D_MODEL=4 };
            int _MaxOptionalRequeriments = 5; //numero de opciones


Y el método iniRequeriments inicializa el array booleano _requeriments a true o a false dependiendo si un requisito se activo o no. Pero, ¿cómo?

Haciendo uso, ahora sí, de los #if.


#if __EXCEL_EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT] = true;
#endif
#if __TXT__EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_TXT__REQUERIMENT] = true;
#endif

Sí por ejemplo el simbolo __EXCEL_EXPORT__ no está definido, entonces 
 _requeriments[(int)OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT] estaría a falso.

La función completa sería:

   private void iniRequeriments()
            {
                _requeriments = new bool[_MaxOptionalRequeriments];
            
                for (int i = 0; i < _MaxOptionalRequeriments; i++)
                {
                    _requeriments[i] = false;

                }

#if __EXCEL_EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT] = true;
#endif
#if __TXT__EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_TXT__REQUERIMENT] = true;
#endif
#if __2D_VIEW__
                _requeriments[(int)OptionalRequeriments.GRAPHICS_2D_REQUERIMENT] = true;
#endif

#if __3D_VIEW__
                _requeriments[(int)OptionalRequeriments.GRAPHICS_3D_REQUERIMENT] = true;
#endif

#if __2D_VIEW__ && __3D_VIEW__
                _requeriments[(int)OptionalRequeriments.GENERATE_3D_MODEL] = true;
#endif

            }

En negrita he puesto el requisito 'generar modelo 3d a fichero'. Como este requisito depende de dos requisitos, no se define un simbolo de tipo __GENERATE_MODEL_3D__.

La gracia de esto, es que se puede establecer una jerarquía de requisitos. Requisitos que se activen o no, dependiendo de otros requisitos!!

Además, de luego, la facilidad para habilitar los menús, dependiendo de las funcionalidades activas. Como se ve abajo.

private void updateMenuFromRequerimentsNewWay()
        {
            txtToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_TXT__REQUERIMENT);

            excelToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT);

            View2dToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_2D_REQUERIMENT);

            View3dToolStripMenuItem1.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_3D_REQUERIMENT);

            generateModel3DToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GENERATE_3D_MODEL);
            
        }

Conclusión

Creo que está forma de activar o desactivar los requisitos de un software, hace más mantenible a la larga el código fuente, ya que, encapsula la complejidad de los #if . Además, esto permite, aumentar la complejidad, pudiendo crear jerarquías de requisitos (activar un requisito cuando varios requisitos están activos, o pueden obedecer a una lógica booleana).


El código fuente se puede encontrar aquí.

Félix Romo
felix.romo.sanchezseco@gmail.com