RailsConf 2013

What Ruby Developers Can Learn From Go

What Ruby Developers Can Learn From Go

by Lionel Barrow

In the talk "What Ruby Developers Can Learn From Go" presented by Lionel Barrow at Rails Conf 2013, the speaker aims to explore the benefits Ruby developers can gain by understanding the Go programming language. With Go becoming known for its efficiency in web development, Barrow argues that Rubyists should consider the advantages of Go's programming paradigms without suggesting a complete shift away from Ruby/Rails.

Key points discussed include:

  • Overview of Go: Barrow introduces Go as a young programming language, emphasizing its design and structure. Developed by Google, it is characterized by its strong typing, garbage collection, and emphasis on concurrency.

  • Programming Style: Go enforces a specific coding style, encouraging developers to follow its guidelines for best practices. This is contrasted with Ruby's flexibility, where various programming styles coexist.

  • Error Handling: A significant focus is placed on how Go encourages developers to handle errors. Unlike Ruby, Go functions commonly return multiple values, including error values, promoting a culture of explicit error checking rather than relying on exceptions. This leads to more predictable and readable code at the cost of verbosity.

  • Dependency Management: The talk discusses Go's approach to organizing and managing dependencies through its package system, simplifying the navigation of code while keeping namespaces clear and manageable.

  • Interface and Composition: Barrow highlights Go's interface-oriented design, which facilitates polymorphism and composition over traditional inheritance, encouraging developers to structure their code in a modular fashion.

  • Conclusions on Language Learning: Barrow concludes that exploring different languages, including Go, helps developers become more adaptable and informed about the trade-offs in their coding practices. The exposure to Go can lead to enhanced understanding of Ruby's idiomatic practices, promoting growth in the programming community.

Overall, the talk encourages developers to experiment with Go while appreciating the strengths of Ruby, fostering a mindset of continual learning and adaptation in programming.

00:00:16.400 All right, hi everyone! I'm going to talk about what Ruby developers can learn from Go. My name is Lionel Barrow. You can find me on Twitter as @atlantalbarrow and you can email me at [email protected]. It's spelled the same way as on my shirt.
00:00:23.439 I work for Braintree, a credit card payment processing company. I'm just curious, do we have any merchants in the room who are Braintree customers? One, two, three, four... all right, great! If you guys can come talk to me after the talk, half the reason why they're willing to send me to a conference is so I can meet with you guys, get feedback, and find out how we can better support our merchants. It would be awesome to hear from you.
00:00:34.240 So, we're going to talk about Go. When I first started writing this talk, I wasn't sure what the right way to go was. Should I start with a whole bunch of programming language discussions, or should I dive right into it? I think I'm going to begin by diving into Go and then step back for the overall discussion.
00:00:40.559 This talk might be a little repetitive for those of you who have already tried out the language, but it should be accessible for everyone. So, what is Go? Go is a new programming language that is compiled, garbage collected, strongly typed, and object-oriented—with a little asterisk on the object-oriented part. It was developed mostly by Google over the last five or six years, and it's actually extremely young; the v1 spec has only been frozen for about 18 months, and I think they just released version 1.1 about two weeks ago.
00:01:06.240 Go is open source, and I think if Google decided to completely axe it, it would still have a future. But it's been driven, in part, by some very talented people and a lot of hard work at Google. It's clear that Go is intended to be a core part of Google's engineering infrastructure for a long time. There's a lot of interest in this language, largely because it has first-class concurrency constructs: goroutines, channels, buffers, and all that sort of stuff. We won't have time to talk about these today, but I'm definitely willing to discuss them in Q&A or if you come up to me afterward.
00:01:30.000 We also won't be able to cover the limited metaprogramming capabilities and the lack of generics, features that you might have heard are a bit messy in the language. However, you will get a sense of why they are missing over the course of the talk. We also won’t go into details about the GoFund, GoFix, GoPath, or any other toolchain-related aspects. Go is almost a regular language; you can parse it quite easily, and this has allowed people to create some very cool tools for manipulating Go from the command line.
00:01:51.759 So, here's the thing to know about Go: it is not very welcoming to a lot of different coding styles. Go has an agenda, and everything about the language is designed to push you towards writing Go the way it expects. This is Rob Pike; he’s one of the leads on Go. The language designers have a very specific way of thinking about how Go programs should be written, and you're going to keep getting prodded and pushed all the way down to force you to write the code in that way.
00:02:30.959 The first thing you have to consider when you're about to write a program in Go is making your peace with this. You have to be okay with it. In my experience, if you accept the effective Go style, then it works; if you try to fight against it, it doesn’t work. Sometimes this means that you have to adopt paths that the language pushes you towards that may conflict with your natural inclination as a Rubyist.
00:02:57.360 I don’t think Ruby has such an agenda. Ruby is syntactically and semantically flexible enough that you can program in many different styles. That doesn't mean we don't have idioms; when was the last time you saw a for loop in Ruby code? You'd likely use array.h, right? Ruby is flexible and does not impose an agenda, while Go does.
00:03:20.559 So, what's Go's agenda? We will explore that by looking at a few specific design decisions about the language and consider how they might look in Ruby. Keep in mind the design goals and why the language designers made these choices: it’s not by accident. There's a great talk by Rob Pike called 'Go at Google,' in which he outlines the specific engineering challenges the language was designed to overcome. It's clear that every design decision is made very deliberately.
00:03:36.640 So, in Go, declaring a variable is different from assigning a variable a state. This may seem simple, but here's how Go source code generally looks: you have your main package and package name at the top of the source file. In the main function that executes, you can declare variable 'a' with syntax 'var a int' and then assign it a value of one. If you try to just say 'b = 2,' you will get a compiler error because 'b' has not been declared. However, you do have a shortcut, where you can declare and assign 'c := 3' at the same time.
00:04:09.040 So what is the design decision here? It aims to eliminate stray variables and dependencies. In Go, it's a compiler error to import a package and not use it. It's also a compiler error to declare a variable and not use it in some way. Such decisions make it easier for you to read the source code and see what you are doing and what you are not doing.
00:04:24.960 The next design decision involves exporting values from packages, which is controlled by the case of the value. Go operates around the notion of a package. If you have ever worked in Python, you'll know that you can import specific values from a source file. In Go, packages span multiple source files and can be seen as a namespace. A package can export certain values to other packages while leaving others internal.
00:04:49.839 For example, we have a Braintree package with a credit card struct. This struct is somewhat like a class declaration in that it specifies the attributes of the class. However, the methods of the class are not necessarily declared in the same block of code. Therefore, if 'last four' is a public attribute of 'CreditCard,' 'number' is not, as indicated by its casing. The same rules apply at the package level: 'CreditCard' and 'ChargeCard' are public, while 'ValidateCard' and 'RunTransaction' are not.
00:05:17.600 The client code for this package might look like this: you have your main function, import the Braintree package, and then say, 'myCard := Braintree.CreditCard{...}' with some last four numbers. You can then use 'Braintree.ChargeCard.' Notice that all the functions here are namespaced, and yes, it all works out pretty well.
00:05:37.920 So, what is the goal of these design decisions? Again, going back to the design intention, the goal is to focus you on the package's API. You really don't need to care about how the internal operations are structured in Go. Instead, you only need to concern yourself with the public interface. This interface is not limited to the methods of a class. You can have various types, the equivalents of objects, with methods and internal state that you can manipulate. The interface of your program is not constrained to just an object.
00:06:05.440 Now, let's discuss interfaces in Go. You might declare an interface for 'animal,' for instance. An interface is a collection of methods, and if your object has all of those methods with the required signatures, then it is said to fulfill that interface. This allows it to be passed polymorphically to any function that uses that interface. For example, you have a 'Predator' interface that can eat animals, and a 'Lion' struct that implements this interface. This setup allows for extensible code—though it’s not true duck typing because the compiler can't know ahead of time what specific type will be passed in.
00:06:37.360 So, when working with Go, you emphasize polymorphism and composition over inheritance. Go doesn't support traditional inheritance; if you declare a struct type with only one member variable, you gain access to the methods of that member variable, but it's not explicit inheritance. This could pose a challenge for Go developers, as the language constrains you to use composition over inheritance. You should ask yourself if you are comfortable with this approach, especially considering its effectiveness.
00:07:05.360 The first three points I mentioned are not particularly controversial, as many developers have encountered similar concepts before. The next point, however, has been hailed as a significant regression in Go. Go utilizes a programming technique called destructuring binding, which isn't anything new but does mean that functions return multiple values. What makes this unusual in Go is that it's common for Go functions to return an output value along with an error—and you check for the error to determine if something has gone wrong.
00:07:26.480 Although Go has an exception-like mechanism, it is strongly discouraged for regular use. The philosophy in Go is that exceptions should only be utilized for truly extraordinary circumstances, like the inability to write to the hard drive. The attitude is that errors should not be viewed as unusual but rather as part of normal control flow in your programs. Here's an example to clarify: in the main package, you would import 'errors,' 'fmt,' and 'math/rand' and perform a calculation that returns both a string and an error.
00:07:51.680 The syntax for multiple return values in Go involves declaring them in parentheses. If 'math.rand' returns a value greater than 0.5, you return 'success' and 'nil' for no error. Otherwise, you return an empty string and an error. In your client code, you would check if the error is 'nil.' If it's not 'nil,' you handle the error. Otherwise, you go on with your life. The idea is to treat errors as usual occurrences rather than exceptions.
00:08:23.679 This bytecode approach means no surprises in your code—unlike Ruby, where it's not always clear when a standard function might return an error. In Ruby, we might rely on conventions, like methods ending in a bang (!), to signify potential errors. While this Go approach makes it clear where errors might arise, it also introduces trade-offs. Each 'error value' line adds noise to the code, making it less concise than it could be.
00:08:44.160 The verbosity of specifying error checks adds clutter. For instance, the main function could be only two or three lines of code if we ignored error values. If errors were treated as exceptions, we could simply let the complex operation terminate normal operation if it encountered an error. Another significant concern is how to effectively handle errors—whether to catch an error and re-raise it as more specific or to propagate it up without modification.
00:09:12.560 When writing middleware, if you catch an error from a lower-level function, should you re-raise it with additional context? Or should you pass it up unchanged? The problem arises when you encounter a completely unrelated error from deep in the stack, and determining its cause may prove infeasible. On the flip side, if you catch and re-raise an error, you risk losing context on why it occurred.
00:09:36.960 Another challenge is ensuring that all parts of your system adequately check error values. If you overlook an error, troubleshooting becomes incredibly difficult, especially if it occurs in a library you haven't directly inspected. You could assume everything is saving to the database if you're not explicitly checking error responses, which can lead to frustrating bugs down the line.
00:10:01.680 Moreover, Go’s agenda encourages developers to write small, composable packages that effectively do one thing. This approach raises questions about where error handling should reside and how business logic is structured. MVC frameworks have made a case for keeping business logic in the models, but presentation logic also needs a home. It's challenging to create generic packages that handle all aspects of an application.
00:10:27.679 Here's an example of something I dislike about Go: a 'find by ID' function. When implementing this in a user package, we should import our error handling libraries. In the function, we may obtain a user from the database but, in the event of an error, we might still return a user object. This could confuse client code that doesn’t check the error, mistakenly believing it received a proper user instance when it’s actually just a shell with no real attributes.
00:10:45.480 The language's verbosity becomes an issue when comparing Go's error handling to Ruby's implementation, which is cleaner and more intuitive. Ruby either returns a proper instance or raises an error. This complexity is a trade-off for the benefits of having a compiled, statically typed language. Go excels at efficient type declarations; its only real types are in the method signatures and type conversions.
00:11:09.920 Now, where do we go from here? I’ve shared some advantages of writing Go code along with some downsides that come with the language. We should also reflect on why we care about these differences and what we hope to gain by learning Go. For me, learning Go is enjoyable, but a complete shift to Go for production environments would require deeper consideration.
00:11:26.720 There’s a popular belief that knowing more programming languages makes you a better developer, but it's not about the quantity of languages. Being exposed to more examples broadens your understanding of programming and allows you to abstract the concepts behind it. While it’s essential to learn as many languages as possible, it’s equally important to bring those paradigms and insights back to your primary language.
00:11:42.760 It can be easy to compartmentalize your learning—switching from Ruby mode to Scala mode without acknowledging the relationships between them may hamper your growth. However, learning from different languages enables you to appreciate your main language more fully. You’ll start to see the trade-offs that led to Ruby's design and idioms.
00:12:14.839 The broader programming community varies significantly among languages, including Ruby's traits like expressiveness and abstraction compared to Java's type system. The crucial difference can often be seen within dependency management and deployment practices, especially if you’ve only worked within the Ruby ecosystem. This disparity can lead to misunderstandings regarding community practices and toolchains.
00:12:38.640 This brings me to the importance of community influence on your coding practices. Working with other programmers is a social act. You cannot dismiss the power of your peers as you write and learn. Engaging with different communities and their established practices is vital for your growth as a developer. I want to talk about code comparisons between languages, especially in determining what idiomatic code looks like.
00:13:04.200 The language designers of Go have a specific vision of idiomatic code that may not coincide with the Rubyists’ view of coding practiced 20 years ago. There’s something significant to understand about how language design shapes coding conventions. A popular definition of idiomatic code is that it uses the language effectively—leveraging semantics and syntax judiciously to achieve goals.
00:13:36.960 However, I would argue that idiomatic code is more about community standards—the commonly accepted way of doing something. While there are numerous ways to accomplish tasks in Ruby, there is a much smaller set of patterns that the community tends to favor. Comfort with familiar structures is essential for collaborative coding.
00:14:06.000 To illustrate the importance of familiarity in coding standards, consider the number of keywords in a language like C++. Even the masters who wrote its compilers might not fully grasp how all the keywords interact. Most developers gravitate towards their favorite 20 or so keywords, leading to a lack of idiomatic C++. Communities that converge on standard practices tend to foster better collaboration and understanding.
00:14:29.520 Familiarity allows programmers to quickly read, critique, and improve each other's work. When examining code, one can discern the 'how' and move towards the 'why'—understanding a coder's motivation is where insight happens. The less time spent navigating unclear logic, the more time can be spent analyzing the thought process behind the decisions made in the code.
00:14:49.600 In summary, while I might not appreciate Go’s directive coding style, the language offers valuable insights worth exploring. The syntax and semantics of a language can significantly influence what idiomatic code emerges from it. Is the goal for a language designer to guide programmers towards better practices? Rob Pike may advocate this philosophy—but it’s not necessarily universal across all programming communities.
00:15:34.960 I am particularly fond of the way that Ruby allows us to structure tests elegantly, as shown in RSpec examples. These testing conventions enable us to clearly convey intentions, thanks in part to Ruby’s syntax flexibility. Decisions from the language's earlier designs have collectively created an environment that fosters writing beautiful tests and code.
00:16:09.840 Ultimately, I am hesitant to fully endorse the idea of tightly controlling programming styles, but exploring Go can be a beneficial exercise. I encourage everyone to try writing in Go, especially focusing on how to handle errors appropriately since error handling is a complex aspect of programming. Be critical of your language of choice, but also value the hard work that goes into language design.
00:16:39.119 We have about ten minutes for questions.