1 points par GN⁺ 21 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Il s’agit d’une expérimentation personnelle consistant à migrer un crate d’application web Rust vers Ruby on Rails, portant sur 14 943 lignes de code basées sur Tera et Axum
  • La configuration Rust existante exige un coût de test élevé, avec Playwright E2E, des espaces de noms de base de données isolés, des services mockés et même un crate d’API interne
  • Dans une comparaison via LLM, Rails obtient un score total de 710 contre 480 pour Rust/Axum/Diesel, avec comme points forts la vitesse de développement et la facilité des tests unitaires
  • La conversion en one-shot avec Local Qwen3.6 a pris environ 30 minutes et le code Ruby a été réduit à 3 322 lignes, mais l’exécution n’a pas encore été vérifiée
  • Rails se distingue par ses fonctionnalités intégrées et des tests concis, tandis que le manque de sûreté de typage de Ruby peut être compensé par Sorbet ou par l’ajout de types via des agents

Contexte de l’expérience de migration

  • Un crate d’application web Rust, qui fait partie d’un projet personnel, a été choisi comme candidat à une migration vers Ruby on Rails
    • Le projet complet compte environ 30 000 lignes, et le crate ciblé ressemble à une application web écrite avec Tera et Axum
    • Le code Rust visé par la migration totalise 14 943 lignes et sa compilation prend environ 10 secondes
    • Le code lui-même n’est pas énorme, mais sa structure traîne de nombreuses dépendances
  • La configuration Rust existante a un coût de test élevé
    • Les tests E2E nécessitent une configuration Playwright
    • Comme le mocking est difficile, il faut des espaces de noms de base de données isolés et des services mockés
    • Un crate d’API interne séparé est aussi nécessaire pour que Playwright interagisse avec l’application en mode headless
  • Ruby et Ruby on Rails sont envisagés comme une alternative plus concise
    • Ruby n’est pas typé, ce qui peut le rendre moins sûr que Rust
    • L’utilisation de Sorbet permet de compenser en partie la sûreté de typage côté Ruby
  • Une comparaison menée avec plusieurs instances de LLM sur la complexité, la stabilité, la facilité de test et d’autres critères donne un meilleur score à Rails
    • Le total de Rust/Axum/Diesel est de 480, Rails obtient 710 et Rails + Sorbet 695
    • Rails est très bien noté pour son adéquation à un développeur solo à 90, sa vitesse de développement à 90 et sa facilité pour les tests unitaires à 90
    • Rust/Axum/Diesel est très bien noté en sûreté à 95 et en performances à 95, mais faible sur la facilité des tests unitaires à 20 et des tests d’intégration à 30
    • Sur la base de la simple somme des scores, l’auteur estime qu’une application Rails pourrait produire un résultat 1,47 fois meilleur

Résultats de la conversion et points à examiner

  • Un projet relativement petit a été converti en one-shot avec Local Qwen3.6
    • La conversion a pris environ 30 minutes
    • Rien n’a encore été exécuté, donc le bon fonctionnement réel n’a pas été confirmé
  • Le changement le plus important est la réduction du nombre de lignes de code
    • Total des lignes dans les fichiers Rust : 14 943 lignes
    • Total des lignes dans les fichiers Ruby : 3 322 lignes
    • Le volume a diminué de 77 %, soit environ 4,49 lignes de Rust pour 1 ligne de Ruby
  • Le code Ruby converti semble propre et idiomatique dans le survol qui en a été fait
    • Des bugs restent possibles
    • Un examen plus approfondi est prévu ensuite
  • Les autres points à examiner sont le renforcement du typage, les fonctionnalités intégrées de Rails et la simplification des tests
    • L’ajout de types à l’aide d’agents peut atténuer les problèmes de sûreté de typage
    • Ruby/Rails est considéré comme plus proche de « batteries + kitchen sink included » et préférable à 3 GiB de dépendances compilées
    • Les tests devraient devenir nettement plus faciles
  • L’exemple de test Ruby est une forme courte qui encapsule l’appel LLM avec VCR.use_cassette("llm_call") et vérifie la taille du résultat
      VCR.use_cassette("llm_call") do
        result = LlmClient.match(entry, data_list)
        expect(result.results.size).to eq(data_list.size)
      end
    
  • L’exemple de test Rust est plus long, car il faut implémenter directement un fournisseur mocké
    • Il utilise Arc<RwLock<Vec<Response>>>, AtomicUsize, async_trait, tokio::test, etc.
    • Il faut créer un MockProvider qui gère la liste des réponses et le nombre d’appels, implémenter ensuite match du trait Provider, puis vérifier dans le test le résultat et le nombre d’appels
  • Comme il s’agit d’un projet personnel, des choix plus audacieux sont possibles, et le passage de Rust à Ruby sera examiné de près à l’avenir

1 commentaires

 
Réactions sur Hacker News
  • J’ai du mal à y croire. On dirait : « J’avais un petit point technique qui me démangeait, une IA locale a bouclé le boulot en 30 minutes. Je n’ai même pas cliqué sur Start pour voir si ça tournait, mais j’ai quand même écrit un billet de blog… »

    • Le fait que ce soit n°1 en page d’accueil veut dire qu’au fond, peu importe que le projet fonctionne réellement. Les lecteurs n’ont probablement pas lu l’article correctement non plus
      à moins que ça n’ait été poussé par des bots
    • 2016 : regardez ma nouvelle bibliothèque JavaScript !
      2026 : regardez ce que je n’ai pas écrit !
      2036 : comment j’ai écrit 200 lignes en C, cette ancienne forme de latin
    • Plus que « difficile à croire », ce sera presque banal d’ici 2026
    • Cela dit, il a quand même passé 5 heures à relire. Donc ¯_(ツ)_/¯
  • Au début, je pensais que ce serait un article intéressant, puis dès que j’ai vu qu’il avait utilisé un LLM pour la conversion, j’ai immédiatement perdu tout intérêt. C’est un peu comme dire : « J’ai demandé à un subordonné de le faire, et maintenant je vais vous raconter l’histoire. »
    Comme il n’a ni fait la conversion lui-même ni vraiment réfléchi en profondeur, je ne vois pas trop pourquoi je lirais ça

    • Le plus gros problème, c’est que l’auteur n’a même pas vérifié. J’ai déjà vu de gros outils qui enchaînent plusieurs modèles faire tourner une conversion pendant 6 heures, puis quand on vérifie manuellement comment ils ont traité des calculs ou de la logique un peu délicats, on découvre des stubs ou des retours vrais codés en dur
      Du coup, un simple smoke test peut donner l’impression que tout a marché
    • Le problème plus large, c’est que c’est ainsi que va évoluer le développement logiciel
      En dehors de la programmation artisanale, le langage lui-même va devenir assez secondaire
      À mesure que les LLM s’améliorent, il s’agira surtout de décider dans quel type de langage générer la spécification
      En un sens, le camp UML et RUP a fini par prendre sa revanche
    • Le point important, ce n’est pas s’il a écrit le code lui-même, mais qu’il a comparé les différences et fait un choix probablement non optimal
      Comme d’autres commentaires l’ont dit, il a fait une revue assez large. Ce n’est pas un gros projet, et à part quelques morceaux épicés, c’est surtout une web app
      Dire qu’il « n’a pas réfléchi » me paraît injuste. Il n’a pas juste appuyé sur un bouton en mode YOLO
      Il a étudié les compromis et les résultats, les différences entre les morceaux de code Rust et Rails étaient réelles, et la testabilité de l’app Rust était un sujet sur lequel il réfléchissait depuis 2 mois
      Comme disent les amateurs de LLM, le contexte compte ;)
  • Je ne suis pas sûr qu’il existe un langage ou un framework qui fasse autant passer le bonheur des développeurs en priorité que Ruby on Rails

    • J’ai rarement été aussi malheureux qu’en travaillant sur un projet Rails. Je vois un bug sur le site, je lance un grep pour trouver la vue mal rendue. Je repère l’appel de méthode qui rend cette section, puis je refais un grep avec le nom de la méthode, et j’obtiens zéro résultat
      C’est un truc composé quelque part, impossible de savoir où, et au final je dois arrêter ce que je fais pour aller lire la doc pendant une heure. Pour quelqu’un qui fait du Rails toute la journée, ça passe peut-être, mais pour moi, la convention plutôt que la configuration est un énorme anti-pattern
    • J’ai une impression similaire avec Elixir et Phoenix, sauf qu’il n’y a pas cette arme pour se tirer dans le pied qu’est method_missing
    • Même si ça ne paraît pas séduisant dans les communautés aux goûts très Silicon Valley, j’ai aussi très bien travaillé avec des frameworks Java et .NET
      Le bonheur ne se traduit pas toujours en performances. Ça me rappelle le célèbre cas du logo de Twitter avant sa migration vers la JVM et Scala
      Ruby on Rails s’est fait un nom, mais j’avais déjà connu des expériences similaires avec AOLServer et Vignette basés sur Tcl
      Dans une startup portugaise, ils avaient même créé leur propre variante, et ses fondateurs ont ensuite lancé OutSystems. C’était l’un des premiers outils RAD graphiques pour développer des sites web et des systèmes distribués, en low-code/no-code sur infrastructure JVM ou CLR
      Cela dit, je suis quand même content de voir que le JIT de CRuby devient maintenant intégré par défaut
    • Au mieux, c’est du bonheur à court terme, payé au prix de quasiment toutes les autres caractéristiques architecturales : maintenabilité, performances, fiabilité, extensibilité, etc.
  • J’ai créé un ensemble de gems appelé propel_rails qui pousse encore plus loin le côté concis du code Ruby on Rails. Il génère des classes de haut niveau comme des contrôleurs d’API et des concerns, puis à partir de là il produit toute une ressource RESTful (modèle, contrôleur, sérialiseur, tests unitaires et tests E2E) sans le moindre boilerplate
    Au final, les contrôleurs ne contiennent plus que la liste des attributs autorisés par l’API, puisque les actions RESTful sont générées automatiquement. C’est un peu difficile à expliquer complètement, mais la puissance de la métaprogrammation en Ruby permet vraiment de faire facilement des choses impressionnantes

    • Ça ressemble à une version raffinée de CRUD
      Ça fonctionne si on raisonne à partir du modèle de domaine ?
    • On le trouve sur rubygems, mais le lien GitHub associé renvoie une 404
  • Je suis dans une situation assez proche
    J’aime Go et Rust, ce sont tous les deux d’excellents langages avec leurs forces et leurs faiblesses. Mais malheureusement, je n’ai réussi à construire aucune app SaaS avec l’un ou l’autre. J’ai l’impression d’essayer de faire entrer une cheville carrée dans un trou rond
    Je peux me tromper lourdement, mais les outils SaaS traînent énormément de choses autour d’eux, et je n’ai pas envie de tout réinventer
    RoR est « suffisamment bon ». On peut ajouter du typage quand on en a envie, on peut aller vite, et l’outillage tient la route
    Mon premier travail pro, c’était du PHP, et il y avait bien trop de pièges. Ruby me semblait un peu plus orienté e-commerce, ce qui m’a intrigué, et si c’est assez bien pour Shopify, alors ça me va

  • Si passer de Rust à Ruby a du sens dans ce cas, alors avoir choisi Rust au départ était déjà une erreur

    • C’est un excellent résumé de l’article, et aussi la raison pour laquelle j’ai voulu le partager
  • Ceux qui pensent que Ruby est plus lent que Rust seraient surpris d’apprendre qu’aujourd’hui, Ruby est en pratique plus rapide que Python, tout en restant plus lent que Go ou Rust

    • En général, l’usage mémoire m’a plus souvent importé que la vitesse. La plupart des applications Ruby sont limitées par la base de données, pas par Ruby lui-même
      Mais quand on a plusieurs workers en arrière-plan et que chacun commence à consommer plus de 2 Go de mémoire, ça s’accumule très vite
      J’ai écrit un service Rust en production, et ce qui m’a le plus impressionné, plus encore que la vitesse, c’est qu’il tournait avec 30 Mo de mémoire
    • Je ne vois pas pourquoi quelqu’un qui pense que Ruby est plus lent que Rust devrait être surpris qu’il soit plus rapide que Python. Quel est le rapport ?
  • C’est peut-être provocateur, mais si Rust est pratique pour afficher un QI de 900+ devant les paysans du coin, beaucoup de développeurs intelligents et talentueux n’aiment pas tant Rust que ça. Certains préfèrent créer leur propre langage de programmation et leur propre compilateur plutôt que d’écrire une ligne de Rust
    Je pense à Jai de Jonathan Blow et à Odin de Ginger Bill
    Il y a aussi bien d’autres personnes dont la créativité et la profondeur sont prouvées, qui ont construit des bibliothèques et des frameworks magnifiques et largement utilisés, mais je n’ai pas envie de gaspiller de place ici
    Cela dit, Rust a un super club macho et une fraternité très soudée

    • La communauté Rust est à peu près à l’opposé total d’un « club macho »
  • Claude fonctionne vraiment bien sur les apps Rails. Comme l’a aussi souligné l’auteur de cet article, Ruby permet de faire beaucoup avec peu de code, et Rails utilise la convention plutôt que la configuration, ce qui rend les apps Rails encore plus concises
    Une hypothèse pour expliquer pourquoi Claude écrit bien des apps Rails, c’est l’efficacité en tokens
    J’avais déjà vu ce projet qui essaie de mesurer et comparer l’efficacité en tokens selon les projets, et Rails s’en sortait plutôt bien
    https://felipemrvieira.github.io/SyntaxTax/dashboard/

  • Je suis parfois surpris par la taille des projets. Une base de code de 30 000 lignes, c’est petit ? Je sais que le plafond peut être bien plus haut, mais 30 000 lignes peuvent contenir énormément d’informations et de subtilités de comportement
    C’est peut-être lié à mon parcours, plutôt orienté backend/réseau en Go. Au-delà de 10 000 à 15 000 lignes, il m’a souvent semblé difficile de garder le modèle global du codebase en tête, ce qui devenait déjà assez lourd

    • Ça dépend énormément de ce qu’on construit. Une app SaaS avec plusieurs frontends atteint facilement les 30 000 lignes