Recientemente estaba impartiendo clases y puse un ejemplo sobre una clase Poligono.
public class Poligon {
public Poligon()
{
this._points = new List<Point>( 3 );
}
public void Add(Point p)
{
this._points.Add( p );
}
public int Count => this._points.Count;
public Point this[int i] {
get {
if ( i < 0
|| i >= this.Count )
{
throw new IndexOutOfRangeException( "" + i );
}
return this._points[ i ];
}
}
public override string ToString() => String.Join( ", ", this._points );
private readonly IList<Point> _points;
}
En clases utilizo Rider, ya que es una solución multiplataforma, y resulta que... bueno, como ya todo en esta vida, y cuando digo todo, me refiero a todo, pues ahora viene con IA.
Esto se traduce en que, al pulsar ENTER y teclear el comienzo de una nueva sentencia, o incluso sin teclear nada, Rider te sugiere el código que probablemente vas a utilizar. Supongo que la idea es que 1) vayas más rápido programando, y 2) puedas confiar en código que no tiene errores.
¿No? Pues va a ser que no.
Resulta que, para insistir en el concepto de la necesidad de la encapsulación, decidí crear una propiedad Points, que devolvería una colección con todos los puntos del polígono. Así que escribo public IList<Point> ...
y lo que me sugiere la IA es:
public IList<Point> Points => this._points;
Venga ya.
Tengo que reconocer que fue una forma de reforzar aún más este tema. Reirse de la IA suele ser una buena forma de hacerlo: si vas a devolver una colección encapsulada dentro de una clase, entonces la clase no sirve para nada.
Otra forma de entenderlo es que si realmente pulsáramos el tabulador para aceptar este código, habríamos abierto una puerta de dos hojas al contenido de la clase, mientras que el objetivo central de cualquier clase es que solo se pueda acceder a lo que la interfaz (la colección de miembros públicos), permita.
O dicho de otra forma, a partir de este código:
var p0 = new Point{ X = 11, Y = 12 };
var p1 = new Point{ X = 21, Y = 42 };
var p2 = new Point{ X = 19, Y = 39 };
var p = new Poligon();
p.Add( p0 );
p.Add( p1 );
p.Add( p2 );
Console.WriteLine( p ); // (11, 12), (21, 42), (19, 39)
...podríamos llegar a esto:
var p0 = new Point{ X = 11, Y = 12 };
var p1 = new Point{ X = 21, Y = 42 };
var p2 = new Point{ X = 19, Y = 39 };
var p = new Poligon();
p.Add( p0 );
p.Add( p1 );
p.Add( p2 );
p.Points.Clear();
p.Points.Add( 111, 111 );
Console.WriteLine( p ); // (111, 111)
...código que no tiene sentido, dado que la única forma de modificar la colección que hemos diseñado solo acepta Add(p: Point)
como manera de modificar la colección.
La forma correcta de crear la propiedad Points es una de las siguientes:
public IList<Point> Points => this._points.AsReadOnly();
public IReadOnlyList<Point> Points => this._points.AsReadOnly();
public IList<Point> Points => new List<Point>( this._points
p.);
El primero devuelve lo que es aparentemente una lista normal, pero cualquier llamada a un método que la modifique (Add(), Clear()...), produce una excepción.
El segundo devuelve una lista de solo lectura, de forma que los métodos que la modifican han desaparecido.
La tercera es la más categórica: crea una nueva lista (es decir, duplica el contenido en memoria), de forma que cualquier modificación que se haga sobre ella... se perderá... como lágrimas en la lluvia.
Y si aceptas el erróneo código sugerido, o las dos versiones del código correcto que no devuelven una IReadOnlyList<...>
, entonces a la hora de añadir elementos, lo que nos sugiere al teclear p.
, tras crear los puntos p0
, p1
y p2
, es:
var p0 = new Point{ X = 11, Y = 12 };
var p1 = new Point{ X = 21, Y = 42 };
var p2 = new Point{ X = 19, Y = 39 };
var p = new Poligon();
p.Points.Add( p0 );
p.Points.Add( p1 );
p.Points.Add( p2 );
Console.WriteLine( p ); // nada
Pues lo dicho. Lágrimas en la lluvia.
Lo mejor es que, aunque desactives el plugin, sigue sugiriendo código. En fin...
Top comments (2)
Muy interesante crítica, Baltasar, como todos tus artículos :-).
Claramente, el modelo utilizado por Rider ha sido entrenado con código que no ha pasado un análisis adecuado. Dicho de otra forma, lo que sugiere el modelo es porque varios programadores lo han escrito antes. La utilización de LLMs de forma discriminada produce estos errores. Esto me recuerda el elevado número de ejemplos incorrectos que hay sobre el uso de herencia en orientación a objetos “porque funciona” :-).
Una posibilidad para no caer en ese error es pasar al código una serie análisis de calidad antes de utilizarlo como entrenamiento. De forma contraria, estarían sugiriendo cosas de poca calidad, por el mero hecho de que otras personas las han escrito. La otra alternativa es modificar el algoritmo de búsqueda que utiliza el generador de código (la parte del decoder de la arquitectura de transformers), para solo generar código que cumpla unos estándares, aunque computacionalmente es más costosa.
¡Muchas gracias por compartir tus experiencias!
Has abierto un melón interesante. ¿Se puede filtrar el código que no tiene suficiente calidad? Y es que aquí llegamos a una conclusión paradójica: el código más popular, que es el que te coge la IA, no siempre va a ser el correcto... ¡de hecho, se va a tratar de todo lo contrario!
No van a contratar a alguien con capacidad reconocida para repasar todo el código que se va a sugerir. Lo único que se me ocurre es que se pueda puntuar el código sugerido, y quizás a largo plazo cierto código acabe desapareciendo.
Es totalmente cierto el comentario que haces sobre la herencia, como el código a continuación:
...aunque no cumple el criterio de que la herencia debería establecer una relación "es un tipo de" entre clase derivada y clase base. Y no hablemos ya del principio de sustitución de Liskov... ¿Podemos utilizar una línea en cualquier contexto donde podríamos utilizar un punto?