[HN Gopher] Sorbet and 100% cov makes Ruby refactoring possible
       ___________________________________________________________________
        
       Sorbet and 100% cov makes Ruby refactoring possible
        
       Author : craigkerstiens
       Score  : 72 points
       Date   : 2022-04-25 18:45 UTC (4 hours ago)
        
 (HTM) web link (brandur.org)
 (TXT) w3m dump (brandur.org)
        
       | d4mi3n wrote:
       | This reflects my experience working with Ruby over the years as
       | well. I find it a perfectly pleasant language, but as with many
       | other scripting languages, things get difficult as a project
       | increases in size, complexity, and number of contributors.
       | 
       | Types are a great way to remove certain classes of issues. It's
       | my hope that newer versions of Ruby really push gradual typing
       | features I've been hearing about forward into common use. The
       | productivity gains of preventing all silly Nil and type errors
       | will be enormous.
        
         | valcron1000 wrote:
         | Setting aside all the dynamic stuff that you can't statically
         | check, what does Ruby offer fhat makes it so "pleasant" to use?
         | 
         | I'm trying to understand what a statically typed Ruby could
         | bring to the table.
        
           | andrewzah wrote:
           | There essentially already is a statically typed ruby:
           | Crystal.
           | 
           | https://en.wikipedia.org/wiki/Crystal_(programming_language)
        
             | chrisseaton wrote:
             | Crystal has a look-alike syntax to Ruby. But the semantics
             | (the important bit) aren't even remotely the same.
        
               | andrewzah wrote:
               | What semantics is crystal lacking? Obviously it's not a
               | 1:1 replacement as the ecosystem is different, gem
               | management (shards) is different, etc. Crystal also has
               | union types and concurrency.
               | 
               | For people who want a syntax like ruby +
               | performance/concurrency, but are willing to deal with a
               | different ecosystem and having to do more stuff by hand,
               | Crystal is a nice choice.
        
               | chrisseaton wrote:
               | Local variables, method calls, basics like that are
               | basically entirely different. Take the Ruby specification
               | test suite and try to run it on Crystal, even with adding
               | typing and other minor changes, and see how far you get.
               | 
               | (Ruby also has full concurrency, by the way.)
        
               | sethrin wrote:
               | Your criticism does not seem rigorously considered. Toy
               | programs can often simply be renamed from .rb to .cr and
               | be compiled and run as Crystal code. Crystal is not,
               | however, trying to be a drop-in replacement: among other
               | things, an enforced type system is not a minor change.
               | Compilation is not a minor change to a language either.
               | It's valid to say that you don't like the tradeoffs (and
               | of course ideally you would have a full understanding of
               | what those are), but it's incorrect to suggest that these
               | languages are not extremely similar.
        
               | andrewzah wrote:
               | Crystal has local variables. I didn't mean to imply that
               | ruby & crystal are 1:1 drop-in replacements, but there
               | are a great many similarities. Enough for me as a
               | ruby/rails dev to start on crystal projects with
               | relatively little issue once I learned a bit about the
               | standard library and the ecosystem.
               | 
               | Ruby's way of handling types is abhorrent compared to
               | Crystal, though. Sorbet/RBS are unfortunate systems
               | tacked on after people realized that type systems are
               | actually really good and not really that verbose.
        
           | thr0wawayf00 wrote:
           | The block syntax is a wonderful way to build and chain
           | flexible iterative logic, the object model is fairly
           | straightforward and the standard library strikes a good
           | balance between functionality and brevity. Once you start to
           | pick up on ruby idioms, the language is pretty fun to use
           | IMO.
        
       | pqdbr wrote:
       | It's a shame the article ends with "So as usual, consider not
       | writing Ruby, but if you do, ...". That's worthless advice and
       | many people, myself included, have Ruby as their most beloved
       | programming language.
        
         | afandian wrote:
         | I've not written Ruby since about 2006 except with a stint in a
         | Rails codebase which did nothing to give me any faith. How do
         | you go about e.g. extracting a group of fields and renaming
         | them? Or reorder function arguments? Is there a static tool
         | that can do this, or do you have to do some kind of text search
         | and hope you got them all?
        
           | dymk wrote:
           | There's basically no tool for doing that, aside from relying
           | on tests to tell you a callsite is now incorrect. "find and
           | replace" is... okay, up to a point, but obviously that has
           | its limitations (especially once you start metaprogramming).
           | 
           | That being said, I still love Ruby. I have all sorts of
           | little tools written in it that would have been a pain to do
           | in another language.
        
             | afandian wrote:
             | Coming from a Clojure background which is ... different.
             | But I understand the enthusiasm around similar language
             | features.
             | 
             | But to borrow words from another comment I find Kotlin
             | "ruthlessly productive". Apart from the lambdas, functions,
             | data classes and immutably, it's the ability to quickly and
             | correctly refactor that makes me feel like I can work at
             | speed and try stuff out.
        
         | bestinterest wrote:
         | There is a gap between languages at the moment imo. Ruby is
         | just ruthlessly productive.
         | 
         | Compare
         | https://github.com/benhoyt/countwords/blob/master/simple.rb
         | with
         | https://github.com/benhoyt/countwords/blob/master/simple.go
         | 
         | Hoping new age compiled languages like Crystal & Nim fill the
         | gap of performance, types & productivity. But compile speeds
         | need to be factored in.
        
           | andrewzah wrote:
           | Comparing a trivial <500 LoC program between languages
           | doesn't tell you anything that's useful other than the
           | terseness of the syntax. You might as well chain unix
           | utilities together at that point.
           | 
           | Maintaining a 5k+ LoC java/c#/go/rust/crystal codebase is
           | orders of magnitude simpler than standard ruby. Sorbet/RBS
           | bridge that gap now, but are a pita to use compared to
           | natively implementing a type system. I know with rust/go I
           | get a simple binary at the end to copy over. Rust,
           | unfortunately, has slow compilation times still compared to
           | go, but I really can't stand error handling in go compared to
           | rust.
           | 
           | That said, "ruthlessly productive" is an apt description. I
           | just don't want to have to maintain a large rails codebase
           | again without sorbet/rbs. I'm hoping phoenix/elixir or
           | something in rust catches on.
        
           | lawl wrote:
           | > Hoping new age compiled languages like Crystal & Nim fill
           | the gap of performance, types & productivity.
           | 
           | Yes, go is verbose at times. But the go language server for
           | example lets you not write a lot of the boiler plate you see
           | there.
           | 
           | E.g. instead of typing out a for loop, i'd just (start)
           | typing `foo.range!`, or for sorting `foo.sort!`.
           | 
           | I'm not going to argue that writing it in go even with those
           | would be more terse, just that I think it _looks_ worse than
           | it is.
        
           | gedy wrote:
           | > Ruby is just ruthlessly productive.
           | 
           | Not disagreeing, but the nuance is productive for writing new
           | code/features. It really feels counter productive once you
           | have a large codebase/team and you need to refactor existing
           | apps.
           | 
           | Spent 5 years at a Rails shop and it's crazy how much
           | engineering effort was spent on keeping this app going.
           | Adding typing seems like a nice step to help here.
        
             | klardotsh wrote:
             | Yeah, I disagree with "ruthlessly productive" as a blanket
             | statement because of this. I tend to find that a huge chunk
             | of the time I save at write-time in Ruby
             | (Rails)/Python/etc., I end up repaying at either runtime
             | (nil exceptions) or read/explain-to-other-dev/refactor
             | times, sometimes in multiplicative form (chasing down why
             | something became nil, or a string, or an elephant, but only
             | if the ORM did X, Y, and Z to the DB response, etc. gets
             | ridiculous quickly)
        
           | pphysch wrote:
           | Is Ruby "ruthlessly productive" for building a fast, highly
           | concurrent program that can be deployed to X platforms with
           | minimal fuss? No.
           | 
           | Is Golang "ruthlessly productive" for interfacing with
           | complex relational databases? No, not even with generics.
           | 
           | Tradeoffs, every language has 'em.
        
           | recuter wrote:
           | Well, the go one would compile to an easily deployable binary
           | and actually be able to, you know, count a whole lot of words
           | quickly. But the ruby one is much more terse and quicker to
           | write.
           | 
           | I say we go back to awk and get the best of both worlds:
           | '{num+=NF} END{print num+0}'
        
             | lambdaba wrote:
             | Ironically half that code (END...) is valid Ruby!
        
           | latenightcoding wrote:
           | The Perl solution would be significantly smaller tho.
        
           | rco8786 wrote:
           | > Ruby is just ruthlessly productive.
           | 
           | This is the best description of Ruby I've ever read.
        
             | princevegeta89 wrote:
             | +1. After working on Elixir for a long time I missed the
             | simple ruby way to do things and found myself running in
             | circles to troubleshoot some basic 3rd party libraries.
             | Going back to Ruby for my next side project.
        
         | rattray wrote:
         | Just because you disagree with it doesn't mean it's worthless
         | advice.
         | 
         | I always have a fond flutter whenever I see Ruby (which I used
         | to write, and really enjoyed) but absolutely wouldn't start a
         | new project with it, for practical reasons. It's slow, it can
         | turn into a big ball of mud as applications scale (there's no
         | named imports for crying out loud, everything is just in a
         | global namespace with side-effects everwyhere), etc.
         | 
         | Certainly there are still people/projects where, upon
         | consideration, Ruby is still the best choice (eg; small Rails
         | shop has a standard CRUD app to build quickly that will not
         | likely ever scale to be huge). But you should still _consider_
         | not using Ruby.
        
         | ecshafer wrote:
         | Absolutely agree. Ruby is a great and beautiful language, and
         | Rails is imo the best web framework for getting things done.
        
           | RTFM_PLEASE wrote:
           | Rails is frankly a double-edged sword, though. While it
           | helped propel Ruby, and is a great framework, it also seems
           | to have permanently marked the language with a notion that
           | its simply a vehicle for Rails.
           | 
           | Ruby is probably one of the more coherently designed general
           | purpose programming languages (in my opinion, much more so
           | than Python, a language that dominates the industry) but
           | doesn't seem to get much use in that domain, which is a
           | shame.
        
             | chris24680 wrote:
             | As a rails developer I really believe if Ruby had at least
             | one more 'Killer App' it would lead to a much healthier
             | overall community.
        
       | mpweiher wrote:
       | The idea that refactoring requires static types is ahistoric and
       | simply incorrect.
       | 
       | The first automatic refactoring tool was the Refactoring Browser,
       | in Smalltalk.
       | 
       | https://refactory.com/refactoring-browser/
       | 
       | https://www.researchgate.net/publication/220346807_A_Refacto...
        
         | recuter wrote:
         | I don't understand why this is the bottom comment instead of
         | the top one.
        
         | a1445c8b wrote:
         | The PDF[1] that the second link points to gives a very good
         | background on why a refactoring tool should be part of any
         | software engineer's toolkit.
         | 
         | Thanks for sharing!
         | 
         | [1] https://www.researchgate.net/profile/Don-
         | Roberts-4/publicati...
        
       | alberth wrote:
       | NIM
       | 
       | I'm still surprised more startups don't use NIM. It has all of
       | the productivity benefits of Python but performance of C.
        
         | brigandish wrote:
         | Did you come to Nim from a Ruby background? I've been using
         | Crystal and am very happy with it but would be interested to
         | know what the jump to Nim might be like.
        
         | [deleted]
        
       | DerArzt wrote:
       | I get what the author is saying, but static analysis aside I
       | would do all that I can to avoid a 178 file update. I get that
       | sorbet is allowing them to do this with higher confidence but
       | even in a language that's compiled (thus performing a similar
       | role to Sorbet) that much change is asking for trouble.
       | 
       | If making a single update touches that many source files, then
       | they may want to take a look at their code organization and
       | architecture as that is a whole heck of a lot!
        
         | shoo wrote:
         | the most productive i have ever been working as a software
         | engineer was in a smaller company where roughly all of the
         | company's product and library code was in a monorepo, and we
         | had a reasonable culture of writing automated tests (although
         | not at 100% branch coverage). sometimes you realise the
         | abstraction in a core library used by all the product code is
         | wrong or limiting, and if your test suite gives you confidence
         | that you're going to catch any potential regressions, you can
         | rework the library with a breaking change & fix everything that
         | transitively depends on that library in a single atomic commit.
        
           | DerArzt wrote:
           | The rub for me here is that making one change in a core
           | library ideally wouldn't touch 100+ other source files. This
           | feels like a separation of concerns issue in which a module
           | is being over used.
           | 
           | Perhaps I'm just in the wrong headspace, as I have never had
           | to work in a monorepo fashion. That being said just because
           | your code is in a monorepo, does that directly mean that you
           | aren't versioning your libraries?
           | 
           | To my understanding most package management tooling in most
           | echo systems allow for versioning. With that said, wouldn't
           | you slowly roll out the version to the downstream projects
           | Monorepo or not?
        
             | swatcoder wrote:
             | Refactoring doesn't just mean optimizing the inside of an
             | implementation.
             | 
             | With a monorepo and good test coverage, I can improve the
             | signature of a function on some library, and use a full-
             | featured IDE to confidently update the usage myself without
             | making it a 10-ticket task that spans 12 teams and 18
             | meetings.
        
             | striking wrote:
             | > This feels like a separation of concerns issue in which a
             | module is being over used.
             | 
             | It's not always modules. Libraries and patterns and
             | frameworks and so on, they will come and they will go.
             | Sometimes you want to just change something across the
             | whole codebase, and having complete type and test coverage
             | over a codebase ensure that you can do so _fearlessly_.
             | 
             | > wouldn't you slowly roll out the version to the
             | downstream projects
             | 
             | No need. The upside of doing so is that you generally
             | prevent breakage, but at the cost of having to support
             | multiple older versions of everything. In a monorepo, you
             | can change your codebase from one language to another in a
             | single commit, and everything should work just fine.
             | (Speaking from experience.)
        
               | shoo wrote:
               | > having complete type and test coverage over a codebase
               | ensure that you can do so fearlessly.
               | 
               | here's a bit of hard-won wisdom. if you have a company
               | codebase in a monorepo where the different components
               | (products, libraries) in the monorepo have a fairly
               | uniform level of quality standards, and there is a
               | uniformly high level of test automation & tooling to
               | detect regressions and enable confidence when making
               | sweeping changes, it is incredibly productive.
               | 
               | however, if you have a company codebase in a monorepo
               | where some regions of the code have wildly different
               | quality standards, and test automation may be patchy or
               | missing from some components, the lack of flexibility and
               | coupling caused by how a monorepo resolves internal
               | dependencies can produce a miserable experience. low-
               | quality code without a good automated test suite or other
               | tools to detect regressions needs to be able to pin
               | versions of libraries, or some other mechanism to
               | decouple from the rate of change of the high-quality
               | components.
               | 
               | E.g. a single developer may be the sole person allocated
               | to a project to attempt to build a prototype to try to
               | win a new customer, so they may be bashing out a lot of
               | lower quality code -- often for a good business reason --
               | without much peer review or test automation. If that
               | lower quality codebase is in the monorepo and depends on
               | core libraries in the same monorepo, then you get a
               | situation where core library developers expect to be able
               | to make breaking changes to core libraries, and if the
               | test suite is green, merge -- but the low-quality
               | prototype codebase doesnt have any tests and just gets
               | increasingly broken (the consultingware prototype code
               | test suite is always vacuously green). Or conversely, the
               | developer trying to get their consulting project over the
               | line might end up telling the core library developers
               | that they're not allowed to make breaking changes as it
               | keeps pulling the rug out from underneath business-
               | critical prototype project delivery, and then you're in a
               | situation where it is no longer possible to refactor the
               | core libraries.
        
             | shoo wrote:
             | > just because your code is in a monorepo, does that
             | directly mean that you aren't versioning your libraries?
             | 
             | If you are living the monorepo trunk-based development
             | dream, you version all of your product and library code
             | with the commit of the enclosing monorepo. The product code
             | contained in monorepo commit X depends on the versions of
             | the libraries also contained in that same monorepo commit
             | X. Maybe another way to say it is that library dependencies
             | are resolved to whatever is checked out in version control,
             | without going through an abstraction layer of versioned
             | package releases pushed to some package repository.
             | 
             | > To my understanding most package management tooling in
             | most echo systems allow for versioning
             | 
             | correct. but that doesn't mean adopting a decentralised
             | package-based versioning strategy is the most productive
             | way for a team to operate!
             | 
             | > With that said, wouldn't you slowly roll out the version
             | to the downstream projects Monorepo or not?
             | 
             | Perhaps! I can think of some arguments why you might prefer
             | a gradual rollout: to reduce effort and split the work into
             | smaller pieces that can be delivered independently, to
             | reduce risk of the change breaking one of N products it
             | touches, forcing the entire change to be rolled back.
             | 
             | But on the other hand, you don't have to -- you can choose
             | to do the refactor atomically, which is not a choice you
             | have if the product and library code is scattered across N
             | different source control systems that depend on each other
             | through versioned package releases.
             | 
             | If you are working in a monorepo & all your internal
             | library dependencies are fulfilled by the coupled library
             | version in the monorepo commit checkout, not decoupled
             | through versioned package releases, then you would need to
             | use different techniques to allow flexibility of some
             | products to depend on version V1 of a library at the same
             | time as other products depend on version V2. The most
             | obvious one is creating a copy of the entire V1 library,
             | giving it a new name, making the V2 change, checking it in
             | to the monorepo as a sibling library, then rewriting some
             | of the products to depend on V2. See also
             | https://trunkbaseddevelopment.com/branch-by-abstraction/
        
           | swatcoder wrote:
           | Yup.
           | 
           | A project that can absorb refactors safely has a lot more
           | headroom than one where every change has to be an incremental
           | one near the leaves.
        
       ___________________________________________________________________
       (page generated 2022-04-25 23:00 UTC)