Une IA qui fait des petits pas ?
Le développement piloté par les tests est une pratique de développement de logiciel principalement connue pour l'écriture des tests avant le code correspondant. Mais c'est presque un détail : même dans un cycle en V, les tests sont décrits avant l'implémentation.
Ce qui caractérise fondamentalement le TDD, c'est d'avancer par de tous petits aller-retours test-code-test-code-test...
Que devient cette approche quand on utilise des assistants de code propulsés l'IA et conçus pour aller vite en prenant souvent des raccourcis ?
On avait poussé un assistant de code à écrire un test élémentaire tout à fait acceptable.
1describe('newProcessStream', () => {
2 it('should extract content inside backquotes', async () => {
3 const stream = [
4 { content: 'This is some text `with content inside` backquotes.' }
5 ];
6 const result = await newProcessStream(stream);
7 expect(result).toEqual(['with content inside']);
8 });
9});
Dans une séquence de TDD, le code minimaliste qui fait passer ce test est simple à écrire. Pourtant, si on demande à plusieurs assistants "Ecris la fonction minimale qui fait passer ce test", on obtient un code qui attaque directement avec des expressions régulières. Par exemple :
1export async function newProcessStream(stream: any[]) {
2 return stream.flatMap(item => {
3 const matches = item.content.match(/`(.*?)`/g);
4 return matches ? matches.map((match: string | any[]) => match.slice(1, -1)) : [];
5 });
6}
Le minimalisme n'est pas là. On pourrait presque dire que ces IAs veulent être trop intelligentes en anticipant un besoin supposé - ce qui arrive aussi aux humains, il faut bien le dire.
On va passer presque autant de temps à essayer de comprendre ce code qu'à l'écrire. Il faut être rapidement capable de vérifier ce que propose l'assistant et là, on n'y est pas. On propose donc à l'IA une alternative.
Je te propose un code bien plus simple qui fait passer le test :
1export async function newProcessStream(stream:any) { 2 return ['with content inside']; 3}
On a 2 types de réactions. La première est étonnament très humaine.
C'est vrai que ce code fait effectivement passer ce test spécifique, mais il ne s'agit pas d'une solution généralisée. Je t'explique pourquoi et quelles sont les différences importantes avec ma solution précédente.
(blah blah) hardcoding des résultats (blah) non extensible (blah blah)
Pourquoi ma version est-elle meilleure ?
(blah blah)
Ton code est une solution minimale mais incomplète
(blah blah)
Je reste à ta disposition si tu veux une explication plus détaillée ou si tu veux discuter d'autres alternatives ! 😊
Bref, je me suis fait AIsplainer.
Heureusement d'autres assistants sont plus réceptifs.
C'est un bon rappel qu'il faut toujours :
- Bien lire les tests
- Ne pas sur-ingénierer la solution si ce n'est pas nécessaire
- Écrire plus de tests si on veut s'assurer que la fonction gère réellement tous les cas d'usage prévus
Cette approche est souvent utilisée en TDD (Test-Driven Development) : on commence par la solution la plus simple qui fait passer le test, puis on ajoute des tests plus complexes qui nous forcent à améliorer la fonction.
Continuons avec cet assistant là en rajoutant un test.
1 it('should extract different content inside backquotes', async () => {
2 const stream = [
3 { content: 'Here is `another text` to extract.' }
4 ];
5 const result = await newProcessStream(stream);
6 expect(result).toEqual(['another text']);
7 });
Et, magie, l'assistant qui avait démarré en sortant les regexp propose désormais une solution minimaliste pour faire passer les 2 tests.
1export async function newProcessStream(stream: { content: string; }[]) {
2 return [stream[0].content.split('`')[1]];
3}
Je reste sur une impression mitigée. On sent que les petits pas sont possibles avec certains assistants et si le besoin leur est bien amené. Mais ça fait beaucoup de contraintes. J'espère que ce type de fonctionnalité sera fera plus courant avec le temps mais, considérant le marketing actuel autour des assistants de code, on peut en douter.