00:00:00.240
Hello everyone, thank you so much for coming to my talk. I'm Gennady.
00:00:05.420
This is how I look online. Here is how you can pronounce my name; it's very long, so you can just call me Kiana.
00:00:12.000
That's perfectly fine. You can find me on GitHub at gsmokevarov and on Twitter as well.
00:00:17.400
I'm from Bulgaria, where I run a Ruby Meetup called Ruby Panitza, named after a delicious pastry made from dough, butter, cheese, and eggs.
00:00:23.400
It's pretty delicious!
00:00:34.160
I am a contributor and work for a company called ForTax, where we make software for accountants.
00:00:40.860
We aim to empower accountants, and I work on our product, Codex Prepare, which automates the most tedious accounting process: data entry.
00:00:46.700
We extract data from financial documents so that accountants don't have to do it by hand, allowing them to send this data directly to the accounting software.
00:00:54.860
We're a multi-product company. Besides Prepare, we also have two other products in our portfolio called DexCommerce and Exposition.
00:01:01.559
Exposition automates data analysis.
00:01:08.159
We are a remote-first company and have been since before the pandemic.
00:01:14.520
We've been doing this for quite a while now, and as a remote-first company, we have a lot of documentation on our engineering practices.
00:01:19.799
Most of our Bulgarian engineers work remotely.
00:01:25.619
I will now discuss our documentation, including processes and style guides.
00:01:31.619
Primarily, I will focus on tax preparation in Ruby on Rails. While we also use Python for some data processing, our information system is built in Ruby on Rails.
00:01:38.880
I love working with Ruby because I have been a Ruby developer for nearly 10 years now.
00:01:45.140
Before working at ForTax, I was at a startup that was acquired by Equals.com.
00:01:51.659
Our team was more familiar with Go, so I spent some time learning and working in Go.
00:01:56.759
What I loved most about Go was its development tooling. Every time I wrote code, I would just run it through Go format, and regardless of my personal style, that's the style we committed to.
00:02:05.280
This approach saved us from spending too much time discussing nitty-gritty style issues. I really appreciated that.
00:02:10.920
As a side note, I manage a project in Go called Jump, which is an auto-jump tool with fuzzy matching. I've been using it for a few years and I really like it.
00:02:18.800
You can start it with blue or snap your fingers.
00:02:24.300
As a remote-less company, we have everything documented.
00:02:31.920
We have documented our previous target, which was a bit peculiar for me when I first encountered it.
00:02:35.640
Let me show you what we do. We implement a lot of water guards and getting expressions.
00:02:42.000
Whenever we have long client assignments, we cut them off onto a new line.
00:02:48.000
We also meet the parentheses on every method call when possible.
00:02:54.540
This is important for this talk because it relates to how we handle Ruby code.
00:03:00.060
We put the dots when chaining method calls on the end of each line, which not many of us practice.
00:03:06.420
We utilize many new Ruby features, which not everyone does, but we should. For example, we use numbered block arguments and value emission without parentheses.
00:03:13.620
These practices can be a bit tricky, and I'll discuss them in more detail later.
00:03:19.560
We may be among the few who consistently use parentheses for method definitions and arguments.
00:03:25.320
Some people say they don't do that, but we do.
00:03:32.220
This is how our Ruby style looks, and when I started at ForTax, I was like, 'Can we look at next?'
00:03:40.259
We have a code review process, and everyone was pretty balanced, which was something I wasn't used to.
00:03:46.380
I thought, 'Can't we automate this? Can't we use RuboCop?' Well, it turned out that there was no such cop.
00:03:51.660
I wondered if we could create this cop ourselves, that way every time we pushed code, we could check if it was formatted correctly.
00:03:57.139
This would help us ensure consistency across our codebase.
00:04:04.800
However, it turns out that it wasn't that easy. There are many situations where we can't omit parentheses.
00:04:10.380
There are also many situations in which omitting the parentheses resulted in syntax errors.
00:04:17.520
And even subjectively, there are a few places where we used or needed to use parentheses. This talk is all about the methods involved.
00:04:23.160
You may have expected a RuboCop talk, but this is mainly a syntax talk.
00:04:30.240
I'm sorry to disappoint, but this is also a discussion about how we solve cultural issues, particularly in code reviews.
00:04:39.420
We focus on stylistic discussions during code reviews, and at the end of the day, we were able to delegate all style discussions through RuboCop.
00:04:45.840
Once we configured RuboCop to our preferences, this process became much simpler.
00:04:51.500
From now on, I will refer to these conventions as the 'Bulgarian style' since that's how we write Ruby.
00:04:58.560
By default, this style is not enforced. It has two styles: the first one requires parentheses on every method call, which is not typical in Ruby.
00:05:05.340
However, if you choose to, you can configure it to require parentheses on every method call.
00:05:12.360
Additionally, I introduced a new option called 'submit parentheses,' which allows parenthesis omission in certain cases.
00:05:19.500
This option applies to calls where parentheses can be omitted while maintaining code clarity.
00:05:25.200
We also have visual flags for situations where we may want to allow parentheses.
00:05:31.380
We can allow parentheses in the last method of a chain, as sometimes this improves readability or is more symmetrical.
00:05:37.920
Moreover, we can allow parentheses in camel case methods, which I will discuss later.
00:05:45.480
We can also allow their use in string interpolation situations, where they enhance clarity.
00:05:53.520
The initial implementation of this cop was introduced in pull request 6469.
00:05:59.640
It first landed in RuboCop proper in version 0.61.0 on December 5, 2018.
00:06:06.600
I started working on this cop on November 20, 2018.
00:06:11.880
This was all before the pandemic.
00:06:18.120
The key contributors to this cop did an excellent job, including Andreas Puman from Germany, who reorganized the code.
00:06:23.640
Kuchito from Japan worked on bug fixes, along with Daniel Vander Was.
00:06:29.340
I apologize if I mispronounced any names—they were all crucial in this project.
00:06:35.940
The scope of this talk is to present to you the various Ruby style methods and how RuboCop interprets them.
00:06:43.320
I will discuss the evolution of the style related to method calls with arguments and parentheses.
00:06:52.560
I will also cover the star hitch syntax cop and how to enforce shortened syntax options, including value emission without parentheses.
00:07:00.480
This feature was introduced in Ruby 3.1 and was quite tricky to implement.
00:07:07.680
Now, let's examine how the parser used by RuboCop operates.
00:07:14.040
This parser is made by White Park and is called 'just parser.'
00:07:21.600
It's a predictive Radio Rep parser written entirely in Ruby.
00:07:27.720
RuboCop has been using this parser since 2013, so for nearly a decade now.
00:07:34.920
This parser is quite effective and capable of parsing different versions of Ruby.
00:07:40.440
Here’s how we can use it: we can require the parser for the current Ruby version.
00:07:46.440
We can then feed it some source code either as text or from a file, and we can get the AST of the source code using the 'pass' method.
00:07:52.320
This is what we have done—I've used this parser to parse the code that RuboCop checks, and here is a textual representation of the AST.
00:07:59.299
This representation is human-readable, and it includes a lot of metadata that we can work with programmatically.
00:08:05.699
We can ask it various questions, such as whether a method call had parentheses or not.
00:08:11.640
In Ruby, when we have multiple expressions, we have an implicit begin block.
00:08:18.419
If we have a method called without an explicit receiver, we have a sent node indicating no explicit receiver.
00:08:25.020
This is the method we want to call, and these are the arguments we want to pass to it.
00:08:31.260
For instance, consider a string literal parsed as the current Ruby method.
00:08:37.080
To assign local variables, we have the local variable initialized to this string literal.
00:08:44.040
Similarly, we have constant implications. When we invoke this method, we call the 'pass' method of the current Ruby constant.
00:08:50.520
Once we obtain the AST, we can show it to developers; this is how RuboCop sees your program.
00:08:56.760
This process illustrates how RuboCop analyzes the codebase.
00:09:03.360
Now, let's discuss when we can omit parentheses in Ruby.
00:09:09.960
In Ruby, we can omit parentheses for method calls without arguments.
00:09:17.600
It's common practice among Ruby developers to leave them out.
00:09:23.520
In the standard library, methods that begin with capital letters are often treated as constants.
00:09:31.920
For instance, the kernel 'Array' method is a method that converts objects into arrays.
00:09:38.520
We can define methods as we choose outside the standard library, but when we call 'Array', this is still considered a method call.
00:09:45.840
We can omit parentheses for keyword arguments or when using splat to pass arguments.
00:09:53.640
If we are calling methods in a multi-line context, we can omit parentheses as well.
00:10:01.100
In certain circumstances, we can use a backslash in the final line of method calls, or alternatively, we can just use parentheses.
00:10:07.240
It's perfectly legal to omit parentheses in super calls and yield codes.
00:10:13.180
Similarly, we can omit parentheses in the last call within a method chain.
00:10:20.800
However, there are many scenarios where we must use parentheses in Ruby.
00:10:27.220
For example, we cannot omit parentheses in keyword arguments.
00:10:32.900
If we pass a keyword argument without parentheses, this code becomes invalid.
00:10:39.000
We also cannot omit parentheses when passing method calls in literal contexts, like array or hash literals.
00:10:44.660
This is essential because omitting parentheses could lead to syntax errors.
00:10:49.120
Similarly, methods like proc or lambda require you to use the dot-parenthesis syntax, which is a shorthand for using the call method.
00:10:56.020
Furthermore, if we encounter a method in a chain, we generally need to include parentheses to avoid ambiguity.
00:11:02.060
Now that we understand the parsing rules, we can analyze the edge cases that we encountered during development.
00:11:09.720
Initially, we found edge cases when omitting parentheses led to ambiguity in our code, which forced us to reconsider our approach.
00:11:16.760
For example, the 'super' keyword can be tricky when called without parentheses because it affects whether arguments are passed.
00:11:23.400
We cannot enforce a one-size-fits-all approach; developers need flexibility based on context.
00:11:29.040
Moreover, we cannot enforce parentheses in keyword arguments and other nuanced syntactical structures.
00:11:39.479
This has led us to re-evaluate when we flag omitted parentheses as offenses.
00:11:46.740
We eventually discovered several edge cases where omitting parentheses was illegal, especially in complex context.
00:11:53.220
We've also encountered cases where methods can be confused with operators, leading to ambiguity.
00:12:01.620
This ambiguity sometimes comes into play with unary operators, leaving it uncertain how to correctly call a method.
00:12:08.940
Consequently, we have learned to keep parentheses consistent across the board, particularly to avoid nesting that affects legibility.
00:12:15.480
With additional refinements, we want developers to maintain intuitive readability in their code, irrespective of style preferences.
00:12:22.520
We've also adjusted our approach to handling parentheses in string interpolation, which can appear ambiguous.
00:12:29.860
In Ruby versions prior to 3.1, there were specific syntax requirements for string interpolation.
00:12:37.920
Now, this has become more flexible with the introduction of value emission in Ruby 3.1.
00:12:44.080
We've learned that in cases of complex expressions, we can allow parentheses to make our intentions clearer.
00:12:52.300
In conclusion, Ruby's syntax allows flexibility, but keeping the code readable is key.
00:12:59.640
We appreciate that developers maintain clear legibility while sticking to controlled styles.
00:13:05.060
Thank you for your attention!