Planet Ruby

Contents

  1. Installing rbenv on Zsh (on MacOS) Antonio Cangiano (Zen and the Art of Programming) @ Penticton, BC › Canada • a day ago
  2. This week in Rails - horizontal sharding, gzip schema cache, database rake tasks Riding Rails • a day ago
  3. NEW DATE! - RubyKaigi @ Nagano, Japan Announced Ruby Conferences 'n' Camps in 2020 - What's Upcoming? • a day ago
  4. RGSoC 2020 student applications are open! Rails Girls Summer of Code Blog • 3 days ago
  5. Benchmarking Ruby 2.7.0's Numbered Parameters Jamie Schembri – Jamie Schembri • 3 days ago
  6. RubyMine 2020.1 EAP6: Unified UI for SSH Configuration RubyMine Blog • 4 days ago
  7. Faster Excel Parsing in Ruby Jamie Schembri – Jamie Schembri • 4 days ago
  8. Introducing Hanami::API Hanami • 5 days ago
  9. Ruby Hash#transform_keys now accepts a hash that maps existing keys to new keys Saeloun • 5 days ago
  10. Open-sourcing Ferrum: a fearless Ruby Chrome driver Evrone Ruby on Rails • 6 days ago
  11. Developer at scale RubyCademy • 6 days ago
  12. Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block The Official BigBinary Blog | BigBinary • 6 days ago
  13. Rails has added strict loading mode to prevent lazy loading Saeloun • 6 days ago
  14. Deep dive into rackup • 6 days ago
  15. Hash#shift using default values RubyCademy • 7 days ago
  16. Rails adds config rake_eager_load Saeloun • 7 days ago
  17. How to test static sites with RSpec, Capybara, and Webkit Notes By Strzibny • 7 days ago
  18. Podcast: entrevistando Fabio Akita Lucas Caton • 8 days ago
  19. Clean your Rails routes: nesting RubyCademy • 10 days ago
  20. React Native vale a pena? – DesBugando #2 [Podcast] OneBitCode • 10 days ago
  21. Strict loading in Active Record and more Riding Rails • 10 days ago
  22. When used intelligently, Rails concerns are great Code With Jason • 11 days ago
  23. The Evolution of Ruby Strings from 1.8 to 2.5 RubyCademy • 11 days ago
  24. Scope Gates in Ruby: Flat Scopes RubyCademy • 11 days ago
  25. Unlearn programming to learn Ruby RubyCademy • 11 days ago
  26. How Rails generates UUIDs by extending the digest library RubyCademy • 11 days ago
  27. How to keep your routes clean in Ruby on Rails? RubyCademy • 11 days ago
  28. ROM and Dry Showcase: Part 4 The Life of a Radar • 11 days ago
  29. [Akitando] #69 - Respondendo suas Perguntas sobre Carreira | via Instagram Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  30. [Akitando] #68 - Entendendo Conceitos Básicos de CRIPTOGRAFIA | Parte 2/2 Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  31. [Akitando] #67 - Entendendo Conceitos Básicos de CRIPTOGRAFIA | Parte 1/2 Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  32. [Akitando] #66 - Entendendo Supremacia Quântica Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  33. [Akitando] #65 - A Dor de Aprender | Que Cursos/Livros? Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  34. [Akitando] #64 - Começando na Carreira de TI | Faculdade? Níveis de Experiência? Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  35. [Akitando] #62 - Aprendendo "Fotografês" | Brinquedos de Miami Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  36. [Akitando] #60 - Entendendo WSL 2 | E uma curta história sobre Windows NT Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  37. [Akitando] #61 - Meus Primeiros 5 Anos | 1990-1995 Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  38. [Akitando] #63 - Não Terceirize suas Decisões! | A Lição MAIS Importante da sua Vida Fabio Akita (AkitaOnRails) @ São Paulo › Brazil • 11 days ago
  39. My Git aliases Code With Jason • 12 days ago
  40. 3.0.8 Released RubyGems Blog • 12 days ago
  41. Rails has added support for automatic database connection switching from primary to the replica Saeloun • 12 days ago
  42. Segunda temporada do Podcast! Lucas Caton • 13 days ago
  43. JRuby 9.2.10.0 Released JRuby.org News • 13 days ago
  44. 3.0.7 Released RubyGems Blog • 13 days ago
  45. Ruby 2.7 adds Enumerable#tally The Official BigBinary Blog | BigBinary • 13 days ago
  46. Ruby’s Time vs DateTime classes Chris Mytton @ Bristol › England • 13 days ago
  47. Ruby 2.7 removes taint checking mechanism Saeloun • 13 days ago
  48. Running Docker in Fedora 31 Notes By Strzibny • 13 days ago
  49. How to Work With Directories in Ruby RubyGuides • 14 days ago
  50. Instalação do Ruby & do NodeJS no Ubuntu Linux, usando ASDF Lucas Caton • 14 days ago
  51. RubyMine 2020.1 EAP4: Improvements in Navigation Between Rails Entities RubyMine Blog • 14 days ago
  52. Alumni Interview with Ipshita Chatterjee Rails Girls Summer of Code Blog • 14 days ago
  53. Rails 6.1 adds at option to perform_enqueued_jobs test helper Saeloun • 14 days ago
  54. How I reached 200 subscribers for my upcoming book Notes By Strzibny • 14 days ago
  55. How I use Sublime Text 3 for publishing my Jekyll-based blog Notes By Strzibny • 15 days ago
  56. IBM Summer 2020 Internship Positions Antonio Cangiano (Zen and the Art of Programming) @ Penticton, BC › Canada • 16 days ago
  57. Porque NÃO DESISTIR da Programação hoje OneBitCode • 17 days ago
  58. Rails adds ActiveRecord API for switching multiple database connections Saeloun • 17 days ago
  59. Understanding Rails secrets/credentials Code With Jason • 18 days ago
  60. How to Calculate Tech Debt Using Skunk on GitHub Actions Fast Ruby Blog • 19 days ago
  61. Rails introduces disallowed deprecations in ActiveSupport Saeloun • 19 days ago
  62. Frameworks do Rails Lucas Caton • 20 days ago
  63. Rails has added a benchmark generator Saeloun • 20 days ago
  64. Rails adds support for if_exists/if_not_exists on remove_column/add_column in migrations Saeloun • 21 days ago
  65. This week in Rails - PostgreSQL 11 partitioned indexes support and more! Riding Rails • 22 days ago
  66. Apertando o Start – DesBugando #1 [Podcast] OneBitCode • 24 days ago
  67. Gemifying your style guide to DRY your CSS Fast Ruby Blog • 26 days ago
  68. 7 Major Differences Between Java & Ruby RubyGuides • 27 days ago
  69. RubyNess @ Inverness, Scotland, United Kingdom Announced Ruby Conferences 'n' Camps in 2020 - What's Upcoming? • 27 days ago
  70. Ruby by the Bay (Ruby for Good, West Coast Edition) @ Marin Headlands (near San Francisco), California, United States Announced Ruby Conferences 'n' Camps in 2020 - What's Upcoming? • 27 days ago
  71. Rails 6.1 introduces class_names helper The Official BigBinary Blog | BigBinary • 27 days ago
  72. What kinds of Rails tests I write and what kinds I don’t Code With Jason • 28 days ago
  73. Instalação do Ruby & do NodeJS no Windows, usando WSL e ASDF Lucas Caton • 28 days ago
  74. ROM + Dry Showcase: Part 3 - Testing The Life of a Radar • 28 days ago
  75. ROM + Dry Showcase: Part 2 - Validations & Transactions The Life of a Radar • 29 days ago
  76. ROM + Dry Showcase: Part 1 - Application + Database setup The Life of a Radar • 1 months ago
  77. How to deploy a Ruby on Rails application to AWS Elastic Beanstalk Code With Jason • 1 months ago
  78. The past and the future of hub Mislav Marohnić @ Zagreb › Croatia • 1 months ago
  79. Why Is It Important to Upgrade Your Rails Application? Fast Ruby Blog • 1 months ago
  80. NÃO seja esses dois tipos de Programadores! OneBitCode • 1 months ago
  81. Issue #19 Ruby Tuesday • 1 months ago
  82. RubyMine 2020.1 EAP is Open! RubyMine Blog • 1 months ago
  83. Ruby Retreat New Zealand (NZ) @ Mt Cheeseman (near Christchurch), New Zealand Announced Ruby Conferences 'n' Camps in 2020 - What's Upcoming? • 1 months ago
  84. Using Elixir Language Server in Sublime Text 3 Notes By Strzibny • 1 months ago
  85. Crowdfunding for 2020 scholarships has commenced Rails Girls Summer of Code Blog • 1 months ago
  86. Ruby ML for Python Coders Shorts • 1 months ago
  87. 16 New ML Gems for Ruby Shorts • 1 months ago
  88. Vale a pena aprender Ruby on Rails em 2020? Lucas Caton • 1 months ago
  89. The Law of Demeter Creates More Problems Than It Solves David Copeland @ Washington, DC › United States • 1 months ago
  90. The Complete Guide to Migrate to Strong Parameters Fast Ruby Blog • 1 months ago
  91. MIR: A lightweight JIT compiler project Vladimir Makarov – Red Hat Developer • 1 months ago
  92. Alumni Interview with Keziah Naggita Rails Girls Summer of Code Blog • 1 months ago
  93. Vídeo: Introdução à JavaScript (JS) em 30 minutos Lucas Caton • 1 months ago
  94. This week in Rails - Rack 2.1 released, disallowed deprecations, and more! Riding Rails • 1 months ago
  95. Getting Started with Rails: Extended Edition The Life of a Radar • 1 months ago
  96. Where to start with introducing TDD to a new Rails app Code With Jason • 1 months ago
  97. Rails Camp West @ Diablo Lake, Washington, United States Announced Ruby Conferences 'n' Camps in 2020 - What's Upcoming? • 2 months ago
  98. RubyConf Belarus (BY) @ Minsk, Belarus Announced Ruby Conferences 'n' Camps in 2020 - What's Upcoming? • 2 months ago
  99. Guide to String Encoding in Ruby Aaron Patterson @ Seattle, WA › United States • 2 months ago
  100. A Migration Path to Bundler 2+ On the Edge of Ruby • 2 months ago

Sunday, 01. March 2020

Antonio Cangiano (Zen and the Art of Programming) @ Penticton, BC › Canada

Installing rbenv on Zsh (on MacOS)

Recently, I updated my Mac to the latest beta of macOS Catalina. It’s not a polished product yet, but overall it’s been a fairly enjoyable operating system.

One of the changes that will impact you as a developer is that Apple switched the default shell from Bash to Zsh (Z shell). You can, of course, change it back to Bash, but I don’t mind Zsh (or Fish) so I decided to keep it.

I needed to install htmlbeautifier, a gem used by some prettifier extensions within Visual Studio Code. If you try it with the default system installation of Ruby, you’ll get a permission error:

$ gem install htmlbeautifier
ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.

At this point, you might be tempted to simply sudo it, but that’s not a smart approach. Instead, I like to have multiple versions through an environment management system such as rbenv or asdf.

For this particular machine, I picked rbenv. Unfortunately, following the usual setup instructions didn’t really work, so I’m sharing the setup that worked for me here.

I updated brew and used it to install rbenv and ruby-build. The latter allows us to select a series of Ruby versions to chose from.

$ brew update && brew install rbenv ruby-build

To ensure that rbenv is correctly loaded when we start a new shell, I had to edit a couple of files. I added the following line to .zshenv:

export PATH="$HOME/.rbenv/bin:$PATH"

This adds gem-specific binaries to the PATH so that they are accessible to the shell without fully qualifying them with their path.

Then I added the following lines to .zshrc:

source $HOME/.zshenv
eval "$(rbenv init - zsh)"

This ensures that the previous file is sourced when a new session is started and that rbenv is initialized for Zsh.

At this point, you’ll want to source .zshrc (or simply restart the terminal of your choice like iTerm2, instead):

$ source ~/.zshrc

With rbenv ready to go, I installed the latest version of Ruby and set it as my global default for this machine:

$ rbenv install 2.6.5
$ rbenv global 2.6.5

After restarting my terminal again, I was able to do a quick sanity test to ensure that the right version of Ruby was being selected:

$ ruby -v
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]

$ ruby -e "puts (1..100).reduce(:+)"
5050

Finally, I was able to install htmlbeautifier:

$ gem install htmlbeautifier

No need for sudo.

I hope this helps anyone who is trying to install rbenv on zsh. It did the trick for me.

The post Installing rbenv on Zsh (on MacOS) appeared first on Programming Zen.


Riding Rails

This week in Rails - horizontal sharding, gzip schema cache, database rake tasks

Greetings, all! Daniel here, together with my pup (🐶 woof!) bringing you the latest news in Rails.

Add support for horizontal sharding

The good folks at GitHub have done an incredible amount of work to support multiple databases in Rails. This week brings horizontal sharding. Rails applications can now connect to and (manually) switch between multiple shards.

Support gzip for the schema cache

Katrina continues to work on the schema cache, this time by adding gzip support for both the YAML and the Marshal serialization strategies. This can come in handy when trying to deploy particularly large schemas in constrained environments.

Add additional multi-database rake tasks

It is now possible to run rails db:schema:dump, rails db:schema:load, rails db:structure:dump, rails db:structure:load and rails db:test:prepare on a specific database. This was previously only possible for rails db:create, rails db:drop, and rails db:migrate. Excellent work on your first few commits to Rails, Kyle!

Eliminate a hash allocation when rendering templates

I included this one for the commit message more than for the code change itself. The benchmark taught me a bit about Action Controller, Action View, and how to write a good benchmark.

That’s all for now. 18 people contributed since last time, including some first-time contributors. Check out the full list of changes.


Ruby Conferences 'n' Camps in 2020 - What's Upcoming?

NEW DATE! - RubyKaigi @ Nagano, Japan Announced

Conferences 'n' Camps

What's News? What's Upcoming in 2020?

NEW DATE! - RubyKaigi
Sep/3-5 (3d) Thu-Sat @ Nagano, Japan • (Updates)

See all Conferences 'n' Camps in 2020».

Friday, 28. February 2020

Rails Girls Summer of Code Blog

RGSoC 2020 student applications are open!

RGSoC 2020 Applications are now open!

RGSoC 2020 student applications are now open! (thanks to Rebecca Conrad for the original illustrations)

It has been a bumper year for project submissions. Our dedicated volunteer reviewers (thanks, team) have spent the past few weeks, in a haze of caffeine, reading, evaluating and discussing all 48 proposals and have just finished making their (often taxing) decisions. Phew!

We thank everyone who entered a project. While we cannot take them all to the next stage, they were all incredibly interesting and had a lot of merit. As a result of being on our Teams App, some people have already started contributing to the projects that weren’t chosen, so they will still benefit from the process. The RGSoC community is amazing! 😍

Students, start prepping!

Now for some really exciting news. RGSoC student applications have officially started on Monday, 02 March 2020.

Here’s your chance to join a global movement that champions underrepresented groups in the field of tech. If you identify as female or non-binary, have at least 12 months’ experience of coding, and possess a passion to progress as a developer then you’re in the right place!

RGSoC offers a:

  • unique opportunity to learn while making valuable OS contributions

  • 3-month community-funded scholarship (01 July - 30 September 2020)

  • network of mentors, coaches, and supervisors who offer code-based and non-technical support throughout the program

  • choice of incredible open source projects to contribute to while you learn

  • chance to kick-start (or change into) a career in programming career with valuable experience on your CV/resume

Sounds good, eh?

Everything you need to know about applying can be found in the student guide so please make sure the first thing you do is read it carefully.

When you’ve organised your teammate, apply in the Teams App at https://teams.railsgirlssummerofcode.org/apply

Don’t forget, your teammate should be living in the same region so that you can meet up every day. And remember, you’ll be working together for many, many hours over the three months, so it is a good idea to pair with someone you get on with well.

Good to know

After the application stage closes on 30 March, our courageous band of reviewers will read each and every entry and face the most difficult part of the RGSoC calendar - choosing the successful candidates.

While we’d prefer to offer a place to everyone, the number of scholarships relies on the success of our fundraising campaign. So if you know anyone who can support it, please refer them to https://railsgirlssummerofcode.org/campaign/ . Who wouldn’t be tempted to see their name in lights in exchange for a small donation? Well, on the RGSoC website, anyway.

To help you make a stronger application, think about including details of: * how long you have been coding and what you already (like to) contribute to * any communities you are involved with, such as meetups, workshops or conferences * dreams, goals, long term plans * details you may have discussed with the project maintainer/mentor that led you to choose the RGSoC project(s) in your application * experience of co-working (good and bad - especially if you resolved any issues)

It’s best to start filling the application form early, even if you’re still searching for coaches and/or a working space.

Your application can be saved as a draft for you to continue and submit any time before the deadline. That’s 23:00 UTC on 30 March 2020, remember. Sorry, but any applications received after that time cannot be considered.

Your choice of projects

We’ve got a whole host of delectable open source projects for you to choose from this year. A list of projects and links to read more about each one is included below. We’re now crowdfunding to ensure as many student teams and projects can partificapte in RGSoC starting in July.

Your coaches should be able to advise which projects would be good for you to choose, considering the languages they involve and difficulty level of the tasks. Of course, it’s also important the project interests you, so always keep that in mind.

Volunteer reviewers

In 2018, we received 195 applications from student teams. We hope to have even half that number again this year, but it also means there is a lot of work ahead of us. It takes time to read and review all those applications, and we can’t do it without help.

If you are interested in joining our reviewer team, then please email us on contact@rgsoc.org. (As much as we appreciate all offers, in the interest of being fair applicants cannot also be reviewers.)

Good luck for your applications. We can’t wait to read them!

Projects

Here’s a brief list of all the selected projects to whet your appetite:

Technologies: machine-learning-algorithms, cpp11, google-test, cuda boost-python

Technologies: Python

Technologies: Benchmarking, ChartJS, Golang, Monitoring, React, Scalability, Web-Sockets

Technologies: Ruby, HTML, CSS, Javascript

Technologies: Flutter, Dart, Firebase, Git, UI/UX

Technologies: Python, Django, React, SQL, Git

Technologies: browser extensions, c++, computer science, java, python

Technologies: UI/UX, React, MongoDB, Angular, databases, Javascript, Nodejs, web-development

Technologies: clojure, R, data science, data visualization

Technologies: css, flask, git, html, java script, python, tensorflow

Technologies: UI, UX, dev ops, ruby, ruby on rails, web dev

  • Name: H2

Technologies: typescript, javascript, electron, tensorflow

  • Name: if-me.org Mental health communication app

Technologies: Ruby on Rails, React, Postgres, HTML, SCSS, CSS, Flow, Storybook, Rspec, Capybara, Jest, Enzyme, Webpack

Technologies: React, Redux, Redux-Sagas, Heroku, Docker, Node.js, Redis, Emotion, Prop-Types, MongoDB, Mongoose, Travis, Sketch, Figma, InVision, Framer

Technologies: Rust

Technologies: Bootstrap, Flask, Jinja, LGPD, MySQL, Python, SQLAlchemy

Technologies: clojure, testing, functional programming

Technologies: nodejs, emscripten, latex

Technologies: Django, JavaScript, Python, Vue

Technologies: CSS, Database, Design, Education, Figma, Git, Github, HTML, JavaScript, Node, Progressive Web App, Rails, React, Ruby, Storybook, UI/UX

Technologies: Javascript, React, NodeJS, GraphQL, MongoDB, SASS Semantic, UI

Technologies: Flutter, Dart, Firebase, Git, UI/UX

Technologies: python, image, visualization, n-dimensional, array, qt, GUI, OpenGL

Technologies: data-structures, algorithms, python3

Technologies: C#, CSS, HTML, Javascrip, Syncfusion, asp.net core

Technologies: C, gaming, graphics, ios, opengl, ruby, tvos, webassembly

Technologies: assembler, compiler, computer science, performance, ruby

Technologies: Machine Learning, Image Processing, GitHub, Natural Language Processing (NLP), Information Retrieval, Python, HTML, CSS, JS

Technologies: JavaScript, CSS, Animation, Webpack, Rails, Rspec, React

Technologies: Python, image analysis, image processing, array

Technologies: nodejs, electronjs, css, html, javascript

Technologies: SASS, SCSS, HTML, JavaScript, Flask, Python

Technologies: Arduino, CSS, HTML, IoT, JavaScript, NodeJS

Technologies: Rails, React, Ruby, Wikipedia, performance


Jamie Schembri – Jamie Schembri

Benchmarking Ruby 2.7.0's Numbered Parameters

Ruby 2.7.0 added numbered block parameters. There was some controversy which resulted in the initial syntax of @n changing to _n. I like the change.

This is what it looks like:

# regular named params
HASH.map { |a, b| [a, b] }

# new numbered params
HASH.map { [_1, _2] }

Anyway, I wondered if there was any performance impact in this change so I threw together a quick benchmark. Result: it’s just as fast as named params.

Warming up --------------------------------------
        named params   143.381k i/100ms
     numbered params   142.011k i/100ms
Calculating -------------------------------------
        named params      2.027M (± 1.6%) i/s -     10.180M in   5.022400s
     numbered params      1.996M (± 2.4%) i/s -     10.083M in   5.053749s

Comparison:
        named params:  2027451.7 i/s
     numbered params:  1996273.3 i/s - same-ish: difference falls within error

Thursday, 27. February 2020

RubyMine Blog

RubyMine 2020.1 EAP6: Unified UI for SSH Configuration

RubyMine 2020.1 EAP6 is now available. In this version, we’ve added a new unified settings tab to manage your SSH configurations.

This tab is located under Preferences/Settings | Tools | SSH Configurations. This is a place where you can add, edit, store, and delete SSH configurations.

Create a new SSH configuration

All these configurations can be picked from a dropbox whenever you need them, for example, if you need to set up the SSH terminal or configure a remote Ruby interpreter.

Pick a saved SSH configuration

We hope that having a single place to manage SSH configurations will make managing server settings easier for you.

What do you think? As always, we encourage you to share your thoughts in the comments below and to create and vote for features in the RubyMine issue tracker.

Early Access Program Key Facts

  • The EAP version of RubyMine is free to use. It will expire in 30 days.
  • This is pre-release software, and it may not work as intended.
  • You can install the EAP version alongside a stable version of RubyMine.
  • EAP versions of RubyMine report statistics by default. These statistics help us improve user experience. You can opt-out by changing the settings in Preferences/Settings | Appearance & Behavior | System Settings | Data Sharing.
  • EAP versions have their own documentation as well.

Join the Early Access Program

You are welcome to download the latest EAP build from our website or via the Toolbox app.

The full list of closed tickets in this EAP build is in the release notes. Please continue to report any issues you encounter.

To learn about new features as they come out, please follow RubyMine on Twitter. We post product news and tips several times a week.

Happy Developing!


Jamie Schembri – Jamie Schembri

Faster Excel Parsing in Ruby

TL;DR: xsv was ~5 times faster than alternatives at parsing the XLSX file I benchmarked it against, and allocates the fewest objects. Meanwhile, roo allocates the least memory of benchmarked gems by a wide margin.


When exporting data for general use, we in the industry are likely to reach for CSV files; they’re basically plain-text, but with a sort-of agreed-upon structure — well, there is RFC 4180 but Wikipedia agrees that implementations are inconsistent at best.

For most people, though, Excel sheets are what’s used and understood. XLSX (aka OOXML) files have been the default file format in Microsoft Office for some years, replacing the proprietary XLS format of yore, and few will look beyond that. Excel sheets can contain considerably more complex information than CSVs, but due to their ubiquity and, perhaps, some level of ignorance, they’re commonly used to transfer simple collections of rows. Long story short: we’re tasked with offering Excel import options in our apps.

And yet, extracting simple data from XLSX files in Ruby is slow. The leading XLSX parser according to The Ruby Toolbox is rubyXL, which is not particularly fast at this task and can suck a ton of memory in the process. Now don’t get me wrong: rubyXL is awesome and can do a lot more than simply read XLSX files, but in most cases that’s all I need.

And my friend, Martijn, is in a similar situation: he just needs to parse simple user-uploaded XLSX sheets that users upload. So he wrote a gem optimized for parsing speed: xsv. I thought the idea was really interesting, so I wrote some simple benchmarks for popular gems capable of parsing these files and tabulated the results below.

The Benchmarks

Gem Parses/second Parses/hour Allocated Memory Retained Memory Allocated Objects Retained Objects
rubyXL 0.129 463 934.014M 1.973M 14.413M 20,215
simple_xlsx_reader 0.282 1,015 462.283M 610.039k 8.21M 5,383
creek 0.422 1,518 911.538M 808.277k 13.180M 6,100
roo 0.301 1,084 168.783M 1.327M 2.232M 11,058
xsv 1.531 5,510 347.250M 362.540k 1.506M 3,166

Benchmarks were run on on a 2018 15-inch MacBook Pro with a 2.6GHz 6-Core Intel Core i7, 16 GB 2400MHz memory and MacOS Catalina 10.15.3. Ruby is 2.7.0, whilst the gem versions are:

  • rubyXL 3.4.12
  • simple_xslx_reader 1.0.4
  • creek 2.5.0
  • roo 2.8.3
  • xsv 0.3.2

Spreadsheet can be found here, provided by the Dutch “Stichtse Vecht”.

I chose not to use the compare feature of benchmark-ips, instead opting to run each benchmark completely individually to ensure no side-effects.

The benchmark code can be found here. Though it’s not part of the codebase, I verified that each benchmark handles the same data by outputting the contents of each cell to a file and comparing the results via cmp.

If you find any errors, please make an issue or a PR.

The Summary

Speed-wise, the benchmarks speak for themselves: xsv is ~5 times faster than alternatives.

Regarding memory use, I’m no benchmarking expert so I’ll just quote Sam Saffron himself on memory metrics (from memory_profiler):

Retained: long lived memory use and object count retained due to the execution of the code block.

Allocated: All object allocation and memory allocation during code block.

roo does a great job in allocated memory, and there may be room for improvement in this area for xsv. Having said that, xsv does come second in memory allocation.

Don’t forget that xsv is only there to parse sheets; for writing and more advanced functionality, you’ll still need to reach for another tool.

Suggestions to improve xsv are welcome.

Wednesday, 26. February 2020

Hanami

Introducing Hanami::API

In the quest of spreading the Hanami word, I talk to many people. A recurring pattern emerged from these discussions: the business risk of "switching" to Hanami, which is a relatively "new" technology. Maybe they want to personally introduce Hanami at their company, but there is the fear of the change.

I understand and respect this feedback, but I believe that people are focusing of the negative side of the story. Here's my positive and reassuring answers.

New techs

There will always be new technologies that are worth to try. In a fast paced world, tech companies risk to fall behind. Developers must be practitioners of the art of coding, it's a must to stay relevant.

This means to pick a few selected technologies and to experiment with them. If you're working with Python, try Go. If you are a Rubyist, play with Elixir. If you use React, build an app with Elm. You name it.

You will never know if the new tech you're looking at, could be come the next important milestone in our industry.

For those who remember, back in the days, we were trying to sneak in our companies this relatively new language from the far east, known as Ruby. Fast forwarding to the present, here we are: Ruby is a well established language.

Iterative adoption

I'm not asking you to convert everything to Hanami, but maybe the next little project that you need to start can use Hanami.

When working on small or greenfield projects, the risk is low, because if something goes wrong, you can always revert to the well known tech.

This iterative adoption approach helps you to discover a new tech, by keeping the risk low.

There is no switch

If Hanami is great for you, it doesn't have be the only tool at your disposal. The opposite is true: instead of having only one way to build web apps, you have multiple options to choose from. Each one has its own pros and cons, just pick the one that makes more sense for your next project.

Introducing hanami-api

To make even lower the risk of adopting Hanami, because it's a full stack framework, we build this tiny HTTP framework: hanami-api.

It's a minimal, extremely fast, lightweight Ruby framework for HTTP APIs.

Our goal is to allow you to try a smaller part of Hanami and build your own stack.

Usage

The usage is very simple and familiar for you. Alongside with the well known Hanami routing syntax, we introduced the block syntax. If you pass a Ruby block to the route definition, then it will be evaluated when an incoming HTTP request will match that route.

Here's a simple config.ru file:

# frozen_string_literal: true

require "bundler/setup"
require "hanami/api"

class App < Hanami::API
  get "/" do
    "Hello, world"
  end
end

run App.new

From the shell you can try it with bundle exec rackup and visit the URL of the app.

As mentioned above, you can still use it alongside with existing Hanami components or any other Rack endpoint:

# frozen_string_literal: true

require "bundler/setup"
require "hanami/api"
require "hanami/controller"

class MyAction
  include Hanami::Action

  def call(params)
    # ...
  end
end

class App < Hanami::API
  get "/", to: ->(*) { [200, {}, ["OK"]] }
  get "/foo", to: MyAction
end

run App.new

Performance

Benchmark against an app with 10,000 routes, hitting the 10,000th to measure the worst case scenario. Based on jeremyevans/r10k, Hanami::API scores first for speed and requests per second, and second for memory footprint.

Runtime

Runtime to complete 20,000 requests (lower is better).

Runtime benchmark

Memory

Memory footprint for 10,000 routes app (lower is better).

Memory benchmark

Requests per second

Requests per second hitting the 1st and the 10,000th route to measure the best and worst case scenario (higher is better).

Requests per second benchmark

Conclusion

hanami-api is simple, low risk, very performant way to try Hanami and build your own Ruby stack. It's the ideal mini framework to build HTTP APIs and microservices.

For more details:

gem install hanami-api today and read the docs to try it today!

Happy coding! 🌸


Saeloun

Ruby Hash#transform_keys now accepts a hash that maps existing keys to new keys

Ruby 2.5 added the methods Hash#transform_keys and Hash#transform_keys! to transform keys of a hash.

Before Ruby 2.8

These methods require a block which is used to transform all the keys of the hash.

Example

2.6.5 :001 > hash = {name: "John", zip: 12345, verified: true}
=> {:name=>"John", :zip=>12345, :verified=>true}

# Returns the hash with transformed keys as per the given block
2.6.5 :002 > hash.transform_keys(&:upcase)
=> {:NAME=>"John", :ZIP=>12345, :VERIFIED=>true}

# The bang sibling of this method mutates the hash in place
2.6.5 :003 > hash.transform_keys!(&:upcase)
=> {:NAME=>"John", :ZIP=>12345, :VERIFIED=>true}
2.6.5 :004 >

In the example above, all the keys of the hash were transformed to uppercase.


After Ruby 2.8 (to be 3.0.0)

Hash#transform_keys and Hash#transform_keys! will now accept an optional hash argument.

The keys of this hash argument represent the existing keys in the hash, and the values represent the new keys.

This allows us to selectively change the keys of a hash like below.

Example

irb(main):001:0> hash = {name: "John", zip: 12345, verified: true}
irb(main):002:0> hash.transform_keys({name: :first_name, zip: :zipcode})
=> {:first_name=>"John", :zipcode=>12345, :verified=>true}
irb(main):003:0>

In the example above, the keys name and zip were transformed to first_name and zipcode. But the verified key remained the same as it wasn’t there in the hash argument.


Passing both an argument and a block together

The hash takes priority over the block when both are passed to the method. Hence, the block is applied only to the keys which were not specified in the hash argument.

Example

hash = {name: "John", zip: 12345, verified: true}
irb(main):002:0> hash.transform_keys({name: :first_name, zip: :zipcode}, &:upcase)
# Only :verified was changed to uppercase as it wasn't specified in the hash argument
=> {:first_name=>"John", :zipcode=>12345, :VERIFIED=>true}

In the example above, the keys name and zip were transformed to first_name and zipcode as specified by the hash argument. Also, the block passed was applied to the only residual key verified and got transformed to uppercase.

Tuesday, 25. February 2020

Evrone Ruby on Rails

Open-sourcing Ferrum: a fearless Ruby Chrome driver


Ferrum is a Ruby gem that controls Chrome through a WebSocket using the Chrome DevTools Protocol, and provides you with a high-level API to it.


RubyCademy

Developer at scale

How the learning curve of a developer can be correlated to the scalability of a startup.

As a developer, there are factors that can impact and limit your learning curve. These factors are directly correlated to the maturity and the current stage of the company.

In this article, we’re going to detail existing situations related to these factor and see what you can do in order to keep improving your learning curve.

The different stages

The different stages that I encountered during my numerous freelance jobs are:

  • startup pre product/market fit
  • startup post product/market fit
  • startup post-funding
  • acquired startup
  • established companies

Startup pre product/market-fit

At this stage, you’ll learn to build and iterate on your product. You’ll learn the basics of the feedback loop. Also, you’ll understand the importance of not over-engineering.

Indeed, as you don’t know what your clients want you’ll have to often modify you code. So writing too much code can be a problem if this code changes or becomes irrelevant after few days.

The limit of this stage, is that as your product isn’t mature, your code misses a bit of consistency and the quality of your code isn’t ensured here.

It’s like a No Man’s Land. Freedom can be either good and bad.

Startup post product/market fit

What’s cool in the previous stage is once you understand what your clients expect from your product, then you can finally consolidate (or rewrite) your codebase by creating a tests suite, adding a process of code review, adding a staging environnement, etc..

Indeed, the act of passing from few users to numerous users is called a Product/Market fit (when your product finally meets its market).

At this moment, your product does few things but it does these things as expected by your clients.

Now, your startup has more ambition, but not enough budget. So, they raise capital for the startup.

Startup post-funding

Now they can recruit. The tech team becomes more structured and plenty of processes are implemented. Indeed, you can’t handle 2 developers like you handle 15. So, processes ensure that each developer fits to the company structure — when they are well implemented of course..

Indeed, an early-stage startup will not have the same problems as a post-funding startup. etc..

Also, this is the moment where you produce the most (in term of features).

The problem here, is that sometimes you won’t be able the produce the same code quality as you’re normally doing because you’re in a phase of quick production. That’s the counterpart..

Also, as you get more and more clients, you learn how to operate and produce code for a larger audience (optimization, architecture, responsiveness, multi-versioning, etc..).

After, few years your company get acquired by an established company.

Acquired startup

It’s particular and rare that you work for an acquired startup.

But this particular stage gives you the opportunity to learn some specific skills.

Indeed, you’ll have to merge your system with another one which is probably very different from yours.

This can be a real pain. But once you achieve the merge, you get a totally different comprehension of crafting an application.

Established companies

The final step of your company is to become an established business.

As a developer, your learning curve depends on the ambition, the culture and the budget of the company.

If they are open to new markets, technologies and are willing to take risk then you can quickly improve your learning curve.

Otherwise, it can become a golden prison where the salary and the perks are good but where learning curve stagnates.

What’s next for you

First, identify at which stage you’re. Then ask yourself this question:

Are you still learning about programming or anything else you’re interested in?

If the answer is NO, then you’ve to be proactive. Here, I list some actions that you can trigger to improve your learning curve:

  • Simply notify your lead developer that you’re eager to learn and that you want to extend your confort zone. Lead developers are generally happy to see your motivation.
  • Pair-program with more experienced developer to learn from them.
  • If you feel that you don’t learn enough quick, challenge the roadmap and ask why the current and next features or projects are redundant.
  • If the company doesn’t see any reasons for improvement, then it’s maybe time to move to a more challenging environment.

Conclusion

The scalability of your company is not the only factor that prevent you to improve your learning curve. but being aware of the current stage of your company can help you to understand what to expect.

Voilà.


Developer at scale was originally published in rubycademy on Medium, where people are continuing the conversation by highlighting and responding to this story.


The Official BigBinary Blog | BigBinary

Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block

This blog is part of our Rails 6 series. Rails 6.0 was recently released.

Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block.

Let’s checkout the bug in Rails 5.2 and the fix in Rails 6.

Rails 5.2

Let’s define an after_commit callback in User model and try updating an invalid user object in a transaction block.

>> class User < ApplicationRecord
>>   validates :name, :email, presence: true
>>
>>   after_commit :show_success_message
>>
>>   private
>>
>>     def show_success_message
>>       p 'User has been successfully saved into the database.'
>>     end
>> end

=> :show_success_message

>> user = User.create(name: 'Jon Snow', email: 'jon@bigbinary.com')
begin transaction
User Create (0.8ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Jon Snow"], ["email", "jon@bigbinary.com"], ["created_at", "2019-07-14 15:35:33.517694"], ["updated_at", "2019-07-14 15:35:33.517694"]]
commit transaction
"User has been successfully saved into the database."

=> #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:35:33", updated_at: "2019-07-14 15:35:33">

>> User.transaction do
>>   user.email = nil
>>   p user.valid?
>>   user.save
>> end
begin transaction
false
commit transaction
"User has been successfully saved into the database."

=> false

As we can see here, that that the after_commit callback show_success_message was called even if object was never saved in the transaction.

Rails 6.0.0.rc1

Now, let’s try the same thing in Rails 6.

>> class User < ApplicationRecord
>>   validates :name, :email, presence: true
>>
>>   after_commit :show_success_message
>>
>>   private
>>
>>     def show_success_message
>>       p 'User has been successfully saved into the database.'
>>     end
>> end

=> :show_success_message

>> user = User.create(name: 'Jon Snow', email: 'jon@bigbinary.com')
SELECT sqlite_version(*)
begin transaction
User Create (1.0ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Jon Snow"], ["email", "jon@bigbinary.com"], ["created_at", "2019-07-14 15:40:54.022045"], ["updated_at", "2019-07-14 15:40:54.022045"]]
commit transaction
"User has been successfully saved into the database."

=> #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:40:54", updated_at: "2019-07-14 15:40:54">

>> User.transaction do
>>   user.email = nil
>>   p user.valid?
>>   user.save
>>   end
false

=> false

Now, we can see that after_commit callback was never called if the object was not saved.

Here is the relevant issue and the pull request.


Saeloun

Rails has added strict loading mode to prevent lazy loading

Rails 6.1 has added strict_loading mode to prevent lazy loading of associations. When the strict_loading mode is used, the associated records will have to be eager loaded using includes else a ActiveRecord::StrictLoadingViolationError will be raised.

>> p = Project.strict_loading.find_by(id: 'abe99c60-56c4-461e-bd59-63418a719e0d')
>> p.commits.to_a
=> ActiveRecord::StrictLoadingViolationError: Project is marked as strict_loading and Commit cannot be lazily loaded.

The strict loading mode cascades down from the parent and helps you catch places where eager loading would prevent N + 1.

>> p = Project.includes(:commits).strict_loading.find_by(id: 'abe99c60-56c4-461e-bd59-63418a719e0d')
>> p.commits.first.post
=> ActiveRecord::StrictLoadingViolationError: Commit is marked as strict_loading and Post cannot be lazily loaded.

The strict_loading mode can also be declared as an option on the association to enforce eager loading of associated records.

class Project < ApplicationRecord
  has_many :commits, strict_loading: true
end

>> p = Project.first
>> p.commits.first
=> ActiveRecord::StrictLoadingViolationError: The commits association is marked as strict_loading and cannot be lazily loaded.

>> p = Project.includes(:commits).first
>> p.commits.first
=> ...

Summary

The strict_loading mode, helps in avoiding N + 1 that might be otherwise missed. Since these are now baked into Rails, we have a better and predictable result, unlike some issues that the bullet gem might face.

Monday, 24. February 2020

Deep dive into rackup

License (MIT)

The MIT License (MIT)

RubyCademy

Hash#shift using default values

Source: unsplash.com

How the way you define your hash default value can generate side effects in your program?

The 3 main ways to declare a default value to a Hash are:

  • by using the Hash.new('default value') method
  • by using the Hash#default= method
  • by using the Hash.new { |h, k| h[k] = 'default value' }

Each of these 3 methods produces a different behavior using the Hash#shift method.

Hash#shift

The hash#shift method removes and returns a pair key/value of a hash

Here, we can see that the initial hash contains a pair foo: :bar.

After the call to hash.shift, the hash is empty and the pair [:foo, :bar] is returned and stored in the pair variable.

Then if we call the hash.shift method on an empty hash nil is returned.

Now that we’re more familiar with the Hash#shift method, let’s see how it behaves when the hash defines a default value.

Hash#default=

Here, we define an empty hash. Then we set the 'val' default value for this hash.

Finally, when we call the hash.shift method we notice that our default value is returned as the hash is empty.

Hash.new(default_value)

Here we define an empty hash using the Hash.new with an argument as default value.

When we call the hash.shift method, we notice the exact same behavior as a hash that sets default value using the Hash#default= method.

Hash.new {}

Here we define an empty hash using the Hash.new with a block that defines the default value.

Then we call the hash.shift method that returns the hash default value.

But a second call to hash.shift returns a pair with nil as key and our default value as value.

Weird..

Not at all. Let’s detail what happens here in the following section.

blocks and Hash#shift

Let’s add few lines to our previous example to understand what’s happens step-by-step

Here, we display the content of our hash after a call to hash.shift and we notice that it contains a nil => 'val' pair.

Then we call hash.shift for the second time and logically this pair is returned.

So, why this pair is inserted in our hash after the first call to hash.shift?

The answer is simple: because the block passed to Hash.new is executed each time that the program tries to access the default value of our hash.

Indeed, the call to hash.shift forces our hash to execute the block passed to Hash.new as its return value is used as default value of hash — when hash is empty.

But the subtlety here is that k in the |h, k| block arguments is nil.

So, the block is processed as following:

  • Step 1: hash[nil] = ‘val’
  • Step 2: The hash#[]= method returns the value passed as argument.
  • Step 3: 'val' is returned as this instruction is the latest one executed in our block.

This is why the first call to hash.shift return 'val' and sets the pair { nil => 'val' } in our hash.

So a second call to hash.shift logically returns [nil, 'val'] and not the default value as the hash is not empty at this stage.

Conclusion

When using Hash#shift or any methods that interact with the default value of the hash, be aware if you use a block as default value for this hash.

Indeed, any assignments or object manipulations within this block can generate side effects that can roughly alter the behavior of your app.

Voilà


Hash#shift using default values was originally published in rubycademy on Medium, where people are continuing the conversation by highlighting and responding to this story.


Saeloun

Rails adds config rake_eager_load

The rake eager loading feature was disabled in Rails in 2013 due to an issue with precompilation. The pull request to enable eager loading in rake tasks was finally merged and is scheduled to be released in Rails.

Lets first understand what happens when eager_load is set to true in rails config. As stated in the Rails Edge Guides:

config.eager_load when true, eager loads all registered config.eager_load_namespaces. This includes your application, engines, Rails frameworks, and any other registered namespace.

Similarly, when config.rake_eager_load is set as true all registered namespaces are loaded when we run the rake task. Let’s look at an example to understand how this works.

class Test
  TIMER = Time.current.strftime("%H:%M:%S")
end

We create a Test class with a constant TIMER which will have the class initialization time.

task test_rake_eager_config: :environment do
  puts "Rails.application.config.eager_load: #{Rails.application.config.eager_load}"
  puts "Rails.application.config.rake_eager_load: #{Rails.application.config.rake_eager_load}"
  puts "Time: #{Time.current.strftime("%H:%M:%S")}"
  sleep(1)

  puts "Time: #{Time.current.strftime("%H:%M:%S")}"
  sleep(1)

  puts "Time: #{Time.current.strftime("%H:%M:%S")}"
  sleep(1)

  puts "Time in Test class : #{Test::TIMER}"
end

We also set up a rake task test_rake_eager_config to test the rake_eager_load config. Lets start by keeping the config.rake_eager_load as false.

Rails.application.config.eager_load: false
Rails.application.config.rake_eager_load: false
Time: 10:34:09
Time: 10:34:10
Time: 10:34:11
Time in Test class : 10:34:12

When the rake_eager_load is set as false the configurations are not loaded as all the registered namespaces are not loaded. Hence the config.eager_load will appear as false even if we set it as true. If you observe the Time, the TIMER constant in the Test class is loaded only when its invoked. Hence the first output for Time is lesser than the Test class invocation but when config.rake_eager_load is set as true.

Rails.application.config.eager_load: true
Rails.application.config.rake_eager_load: true
Time: 10:34:36
Time: 10:34:37
Time: 10:34:38
Time in Test class : 10:34:35

All the configurations in the environment config files are loaded. Hence the actual values are reflected. Besides, if you observe the Time, the initial Time and the final are the same, as the Test class in eager loaded for the rake task.


Notes By Strzibny

How to test static sites with RSpec, Capybara, and Webkit

Automated tests are not only good for dynamic code. You can (and probably should) test your static web sites as well. Here is an example of me testing the Fedora Developer website using Ruby, RSpec and Capybara.

First, let’s briefly stress out why the testing is really needed. Fedora Developer is statically built with Jekyll and the content itself is created by many contributors. As you can imagine, things can break pretty easily. It’s one thing to test the layout and things you are in control of, but another when you want to make sure that people are creating new content as expected.

One of the things we want to make sure of is the correct internal linking of other pages. Not everybody immediately knows how to write a relative Markdown link for the website that is going to be generated later. Even as a reviewer I might be looking it up. Solution? Write tests and run them before the deployment of the new version of the website.

Since Jekyll already implies that Ruby is installed and available, let’s write a Capybara/Webkit specs similarly as if we would test a Rails web application. In the website root we add a new spec directory with the following spec_helper.rb:

# This spec needs rubygem-rack, rubygem-capybara and rubygem-rspec installed.
# Run as `rspec spec/` in the project root directory.

require 'rack'
require 'capybara'
require 'capybara/dsl'
require 'capybara/session'
require 'capybara/rspec'
require_relative './shared_contexts.rb'

class JekyllSite
  attr_reader :root, :server

  def initialize(root)
    @root = root
    @server = Rack::File.new(root)
  end

  def call(env)
    path = env['PATH_INFO']

    # Use index.html for / paths
    if path == '/' && exists?('index.html')
      env['PATH_INFO'] = '/index.html'
    elsif !exists?(path) && exists?(path + '.html')
      env['PATH_INFO'] += '.html'
    end

    server.call(env)
  end

  def exists?(path)
    File.exist?(File.join(root, path))
  end
end

# Setup for Capybara to test Jekyll static files served by Rack
Capybara.app = Rack::Builder.new do
  map '/' do
    use Rack::Lint
    run JekyllSite.new(File.join(File.dirname(__FILE__), '..', '_site'))
  end
end.to_app

Capybara.default_selector =  :css
Capybara.default_driver   =  :rack_test
Capybara.javascript_driver = :webkit

RSpec.configure do |config|
  config.include Capybara::DSL

  # Make sure the static files are generated
  `jekyll build` unless File.directory?('_site')
end

What are we doing here you ask?

  • We are transforming our Jekyll site to be a Rack application (Rack is Ruby HTTP interface).
  • We are using Rack::Builder to create our Capybara application.
  • We are telling Capybara to use :webkit driver.
  • Finally we are configuring RSpec to simply use Capybara DSL (that we use in the actual tests).

You might have noticed two more things.

We are requiring shared context. Our shared context tests the header, search box, and footer that is present on every generated page. Here is the source:

# What every single page should contain
RSpec.shared_examples_for 'Page' do
  it "has top-level menu" do
    expect(page).to have_css("#logo-col a[href~='/']")
    expect(page).to have_link("Start a project", href: '/start.html')
    expect(page).to have_link("Get tools", href: '/tools.html')
    expect(page).to have_link("Languages & databases", href: '/tech.html')
    expect(page).to have_link("Deploy and distribute", href: '/deployment.html')
    expect(page).to have_css("ul.nav li", count: 4)
  end

  it "has footer" do
    expect(page).to have_css(".footer")

    # 4 sections: About, Download, Support, Join
    expect(page).to have_css(".footer h3.widget-title", count: 4)

    # Footer links
    expect(page).to have_link("About Developer Portal", href: '/about.html')
    expect(page).to have_link("Fedora Magazine", href: 'https://fedoramagazine.org')
    expect(page).to have_link("Torrent Downloads", href: 'https://torrents.fedoraproject.org')
    expect(page).to have_link("Forums", href: 'https://fedoraforum.org/')
    expect(page).to have_link("Planet Fedora", href: 'http://fedoraplanet.org')
    expect(page).to have_link("Fedora Community", href: 'https://fedoracommunity.org/')
    expect(page).to have_css(".footer a", count: 27)

    expect(page).to have_css(".footer p.copy", text: /© [0-9]+ Red Hat, Inc. and others./)
  end
end

# Search page does not contain form#search
RSpec.shared_examples_for 'Page with search box' do
  it "has a search box next to the top-level navigation" do
    expect(page).to have_css("form#search")
    expect(page).to have_css("form#search input")
    expect(page).to have_css("form#search button")
  end
end

We are also making sure to run jekyll build to generate the site (we expect and work with standard Jekyll _site directory).

With all of this setup we can write a regular Capybara test:

require 'spec_helper'

# Array of all generated pages
site = File.join(File.dirname(__FILE__), '..', '_site', '**', '*.html')
PAGES = Dir.glob(site).map{ |p| p.gsub(/[^_]+\/_site(.*)/, '\\1') }

PAGES.each do |p|
  describe p do
    it_behaves_like 'Page'
    it_behaves_like 'Page with search box' unless p == '/search.html'

    before :each do
      visit p
    end

    it 'has only valid internal hyperlinks' do
      page.all(:css, 'a').each do |link|
        next if link.text == '' || link[:href].match(/(http|\/\/).*/)
        page.find(:xpath, link.path).click
        expect(page.status_code).to be(200), "expected link '#{link.text}' to work"
        visit p
      end
    end
  end
end

This test will iterate over all our pages, visit them, use our shared context thanks to it_behaves_like call, and finally run any further tests.

In the test example above all the relative links found on the page will be visited by Capybara (we are clicking on them with click()). If such page does not exist, we fail our RSpec test with expectation.

After all of this in place, the idea is to just run rspec spec in the project root directory:

$ rspec spec
............................................................................................................................................................................................................................................................................................F...............F.......F...................

Failures:

  1) /tools/docker/about.html has only valid internal hyperlinks
     Failure/Error: expect(page.status_code).to be(200), "expected link '#{link.text}' to work"
       expected link 'configuring Docker' to work
     # ./spec/pages_spec.rb:20:in `block (4 levels) in &lt;top (required)>'
     # ./spec/pages_spec.rb:17:in `block (3 levels) in &lt;top (required)>'

  2) /tools/docker/compose.html has only valid internal hyperlinks
     Failure/Error: expect(page.status_code).to be(200), "expected link '#{link.text}' to work"
       expected link 'Getting started with Docker on Fedora' to work
     # ./spec/pages_spec.rb:20:in `block (4 levels) in &lt;top (required)>'
     # ./spec/pages_spec.rb:17:in `block (3 levels) in &lt;top (required)>'

  3) /tools/vagrant/about.html has only valid internal hyperlinks
     Failure/Error: expect(page.status_code).to be(200), "expected link '#{link.text}' to work"
       expected link 'Vagrant with libvirt' to work
     # ./spec/pages_spec.rb:20:in `block (4 levels) in &lt;top (required)>'
     # ./spec/pages_spec.rb:17:in `block (3 levels) in &lt;top (required)>'

Finished in 7.8 seconds (files took 0.36521 seconds to load)
328 examples, 3 failures

Oh my, links are broken! Let’s fix them first…

On the Fedora Developer website this call is part of the deploy script:

$ ./deploy.sh
Running specs...
........................................................................................................................................................................................................................................................................................................................................

Finished in 8.17 seconds (files took 0.37417 seconds to load)
328 examples, 0 failures

Checking dependencies...
ruby-2.2.3-44.fc22.x86_64
rubygem-liquid-3.0.1-1.fc22.noarch
rubygem-actionview-4.2.0-2.fc22.noarch
Uploading site from _site/, check that the content is current
about.html                                                            100% 9713     9.5KB/s   00:00
about.md                                                              100%    0     0.0KB/s   00:00
...

Trust me, this feels so much better. Finding the issues before the site is deployed is important both for your visitors and your good night sleeps!

Sunday, 23. February 2020

Lucas Caton

Podcast: entrevistando Fabio Akita

Fabio Akita

Seguindo com a 2ª temporada do meu podcast, dessa vez o entrevistado foi Fabio Akita, um amigo de longa data e um profissional que adimiro muito.

Assista a entrevista completa no YouTube:

Você também pode escutar a entrevista através do seu player de podcast preferido; é só buscar por Lucas Caton no app de podcast ou conferir o link direto nessa página.

Friday, 21. February 2020

RubyCademy

Clean your Rails routes: nesting

Source: unsplash.com

How resources can help you to avoid custom routes?

Feel free to have a look to the Clean your Rails routes: grouping article.

In Ruby on Rails, the routes are strongly correlated to the concept of resources.

But sometimes, you need to create custom nested routes.

So, in this tutorial, we’ll see how to avoid to create these custom routes.

Example

Few month ago, we created a my-blog application. This application allows you creating and manipulating blogposts.

Let’s have a look to the config/routes.rb file

Well, that makes the job.. But it’s not RESTful!

How refactoring this code to ensure that it follows the RESTful guidelines?

The answer is: by using resources!

Here, we simply define a nested resource with only one CRUD action: create.

So, the route will be defined as following: POST /posts/:id/comments and it’ll look for a CommentsController#create action to be implemented.

⚠️ Try not to implement more than one level of nesting ! It could make your code harder to maintain by creating resource dependencies.

Voilà!


Clean your Rails routes: nesting was originally published in rubycademy on Medium, where people are continuing the conversation by highlighting and responding to this story.


OneBitCode

React Native vale a pena? – DesBugando #2 [Podcast]

 

O podcast que veio para exterminar os bugs do seu código e te guiar pelo maravilhoso mundo da programação!

DesBugando #2 – React Native vale a pena?

com:
• Jackson Pires – https://videosdeti.com.br
• Leonardo Scorza – https://onebitcode.com
• Lucas Caton – https://www.lucascaton.com.br

O post React Native vale a pena? – DesBugando #2 [Podcast] apareceu primeiro em OneBitCode.


Riding Rails

Strict loading in Active Record and more

Hi, Wojtek from this side with latest changes in Ruby on Rails codebase.

Add strict_loading mode to Active Record

To prevent lazy loading of associations, strict_loading will cascade down from the parent record to all the associations to help you catch any places where you may want to preload instead of lazy loading.

Serialize schema cache dump with Marshal

In addition to YAML it is now possible to use Marshal as schema cache dump serializer.

Improve assert_changes output

Provides more specific diffs when comparing complex objects. Co-authored by few contributors.

36 people contributed to Rails since last time. Check out the detailed list of all changes. Until next time!

Thursday, 20. February 2020

Code With Jason

When used intelligently, Rails concerns are great

Is a knife a good thing?

There seem to be two main camps regarding Rails concerns. One camp says concerns are good. The other camp says concerns are bad.

I reject this dichotomy. To state what ought to be obvious, not everything is either good or bad. One example is a knife.

You could use a knife to stab me in the leg, or you could use a knife to whittle me a beautiful wooden sculpture.

Or, if you were uninformed regarding good uses for knives, you could use a knife instead of a spoon in a misguided attempt to eat a bowl of soup. (Then you could write a blog post about why knives are terrible because you can’t eat soup with them.)

I want to discuss where concerns might be a good idea and where they might not be. In order to do this it might be helpful to start by articulating which types of problems people tend to use concerns to try to solve.

The problems that people try to solve with concerns

There are two problems I’m aware of that people try to use concerns to solve.

  1. Models that are too big to be easily understood
  2. Duplication across models

Both these problems could be solved in a number of ways, including by using concerns.

Another way could be to decompose the model object into a number of smaller plain old Ruby objects. Yet more options include service objects or Interactors. All these approaches have pros and cons and could be used appropriately or inappropriately.

The objections some programmers have to concerns

Here are some objections to concerns that I’ve read. Some I agree with. Some I don’t think are valid.

Concerns are inheritance, and composition is better than inheritance

I totally agree with this one. An often-repeated piece of advice in OOP is “prefer composition over inheritance”. I think it’s good advice.

Having said that, the advice is not “don’t ever use inheritance!” There are a lot of cases where inheritance is more appropriate than composition, even if inheritance isn’t the first thing I reach for. In the same way, the argument that “concerns are inheritance and composition is better than inheritance” does not translate to “concerns are always bad”.

Concerns don’t necessarily remove dependencies, they just spread them across multiple files

I agree with this point as well. It’s totally possible to slice a model up into several concerns and end up with the result that the “after” code is actually harder to understand than the “before” code.

My response to this point would be: If you’re going to use concerns, don’t use them like that!

Concerns create circular dependencies

As far as I can reason, this is true. A well-defined inheritance relationship doesn’t create a circular dependency because the parent class doesn’t know anything about the child class. Similarly, in a well-defined composition relationship, the “finer-grained” class doesn’t know about the “coarser-grained” one.

Concerns often don’t work this way. Concerns often refer to the particulars of the models that use them.

But I would say: Why, exactly, is this sort of circular dependency bad? And if I choose not to use a concern in a particular case because I want to avoid a circular dependency, what exactly my alternative? The drawbacks of whatever I use instead of a concern aren’t automatically guaranteed not to be worse than the drawbacks of using a concern.

When code is spread across multiple files, it can be unclear where methods are defined

This argument has a very small amount of merit. This argument could be made equally for not just concerns but for inheritance as well. So it’s really not an argument against concerns specifically, it’s an argument against concerns and inheritance equally.

But in any case, the problem can be easily overcome using search.

What I do when I encounter fat or duplicative models

When I encounter a fat or duplicative model, concerns are pretty much never the first solution that enters my mind.

My first instinct, again, is to consider decomposing my model into some number of plain old Ruby objects. I think sometimes Rails developers forget that plain old OOP, using plain old objects, can get you a very long way.

I often find that a large class will have a “hidden abstraction” inside it. There’s some sub-concept hiding in the class that doesn’t have a name or a cohesive bundle of code, yet it’s there.

In those cases I try to come up with a name for the hidden abstraction. Then I create a class named after that abstraction and move the appropriate code into my new class.

I don’t always find a hidden abstraction though. Sometimes some of the bloat is just some extra flab, a love handle if you will, that neither fits neatly into the model it’s in nor makes sense as a standalone concept. This is when I (sometimes) think of using a concern.

When I use concerns

In DHH’s post about concerns he says “Concerns are also a helpful way of extracting a slice of model that doesn’t seem part of its essence” (emphasis mine).

Good use cases for concerns

To me, the perfect use case for a concern is when I have, say, 10 methods on a model that have a high level of cohesion with one another and then 2 methods that relate to each other but (to use DHH’s words again) don’t seem to be part of the model’s essence.

If those 2 odd-man-out methods could make a new object of their own, great. I’ll do that. If not, I might consider moving them to a concern.

And of course, if multiple models require highly symmetrical behavior (e.g. some certain file attachment behavior), I’d usually rather factor that symmetrical behavior into a concern than to an inheritance tree. And usually a concern in these cases fits much more tidily over the models than trying to create a composition relationship out of it.

Lastly, I don’t want to give the impression that I use concerns all over the place. In general when I’m programming, concerns are among the last things I reach for, not the first.

My advice concerning concerns

I think concerns are fine and, in certain scenarios, great. That of course doesn’t mean I think concerns are the best solution to every problem.

When faced with any programming problem, take stock of the tools in your toolbox and use what seems appropriate. Object composition can be good. Inheritance can be good. Concerns can be good too.

The post When used intelligently, Rails concerns are great appeared first on Code with Jason.


RubyCademy

The Evolution of Ruby Strings from 1.8 to 2.5

An overview of the String class since Ruby 1.8


Scope Gates in Ruby: Flat Scopes

uSource: unsplash.com

An overview of the execution context of blocks

In this article, we’re going to explore the following topics:

  • scopes and blocks
  • flat scope guard in class/module
First, feel free to browse the Scope Gates in Ruby: Part II article.

Scope and blocks

In Ruby, a block can access outer scope

In this example, we can access the outer_scope_variable variable within our block.

Our block gets access to this variable even though the variable is declared at a main level scope.

In this case, we say that the block flatten scopes.

This is why we commonly call this mechanism: Flat Scope.

In another hand, the block creates an isolated scope — even if the value of self within the block remains the main object.

So we can’t access local variables defined within the block

Here, we can’t access the block_variable from the outer scope.

Now that we’re more familiar with the notion of flat scope let’s see if this notion impacts class definition.

Class, module and block

In Ruby, what you commonly call a class is, behind the scene, an instance of the Class class

Here, we create the Hello and Greeting classes in two different way:

  • using the class keyword
  • we assign an instance of the Class class to the Greeting constant

Note that we are passing a block to the Class.new method.

This block will be interpreted as the content of the class.

Normally a class can’t access variables defined in the outer scope — as the class keyword changes the value of self and creates an isolated scope.

But as we use a block then (normally) we should be able to use the flat scope mechanism

Here we can see that the value of self changes when using the class keyword.

In another hand, our block is still isolated from the main object.

Indeed, as our block is executed in the context of an object then the block flattens scope within this given object — and not with a higher scope as the main object.

That’s why we don’t have access to outer_variable within the Class.new block.

Voilà!

ONE MORE THING ⬇

Feel free to subscribe here: www.rubycademy.com

Thank you for taking the time to read this post :-)

Feel free to 👏 and share this article if it has been useful for you. 🚀

Also, as I post an article every 3 days, feel free to follow me to be notified of my new releases.


Scope Gates in Ruby: Flat Scopes was originally published in rubycademy on Medium, where people are continuing the conversation by highlighting and responding to this story.


Unlearn programming to learn Ruby

Why the Ruby philosophy is the exact opposite of other programming languages?


How Rails generates UUIDs by extending the digest library

source: unsplash.com

An overview of the UUID specification and the Digest::UUID module

What’s a UUID?

UUID stands for Universally Unique IDentifier — also known as GUID (Globally Unique IDentifier).

It’s a sequence of bits used to ensure identification of a unique resource.

A UUID is 128 bits long, and can guarantee uniqueness across space and time.

There is currently 5 versions of UUIDs.

They are known as version 1, 2, 3, 4 and 5.

Each version implements a set of variations more or less complex.

For example, the UUID v1 specification is based on date-time and MAC address.

On the other hand, the UUID v4 specification defines a set of 122 bits randomly generated.

As you noticed, each version defines its own way to generate the UUID.

Feel free to browse the RFC 4122 for further information about each version.

Now that we’re more familiar with the notion of UUID, let’s see how Ruby on Rails extends the digest library to implement the versions 3, 4 and 5 of the UUID specification.

The Digest::UUID module

This module provides a set of helper methods to generate UUIDs for the versions 3, 4 and 5 – as defined in the RFC 4122 specification

In the above example, we use the uuid_v3 , uuid_v4 and uuid_v5 methods to generate UUIDs.

These 3 methods respectively return a UUID v3, UUID v4 and UUID v5.

The uuid_v4 method simply uses the return value of SecureRandom.uuid.

Indeed, as defined in the UUID v4 specification, 122 bits are randomly generated.

So, this specification is already implemented by the SecureRandom.uuid method.

Now, the UUID v3 and UUID v5 specifications are almost similar.

They are composed in a mix of a namespace and a name that are encrypted using the MD5 algorithm (UUID v3) or the SHA-1 algorithm (UUID v5).

All this logic in encapsulated in the uuid_from_hash method that is internally called by both the uuid_v3 and uuid_v5 methods.

The uuid_from_hash method takes 3 arguments:

  • the encryption algorithm (MD5 or SHA-1)
  • the namespace
  • the name

So when we call

Internally the uuid_v3 method calls the uuid_from_hash method with the following arguments

As defined in the specification, the UUID version 3 uses the MD5 algorithm to generate the hash.

For uuid_v5

As defined in the specification, the UUID version 5 uses the SHA-1 algorithm to generate the hash.

Voilà!

ONE MORE THING ⬇

Feel free to subscribe here: www.rubycademy.com

Thank you for taking the time to read this post :-)

Feel free to 👏 and share this article if it has been useful for you. 🚀

Also, as I post an article every 3 days, feel free to follow me to be notified of my new releases.


How Rails generates UUIDs by extending the digest library was originally published in rubycademy on Medium, where people are continuing the conversation by highlighting and responding to this story.


How to keep your routes clean in Ruby on Rails?

source: unsplash.com

Clean your Rails routes: grouping

How to separate & group your routes by logical entities

In Ruby on Rails, all the routes of a given application can be found within the config/routes.rb file.

You add more and more routes in this file as your project grows.

The problem here is that this file potentially become very complicated to manage over the time.

That’s why it’s important to find a way to order and maintain your routes.

Let’s have a look to an example to see how we could achieve this task.

The my-blog application

Few month ago, we created our my-blog application. This application allows you creating and manipulating blogposts.

Also, it provides an admin dashboard.

So the config/routes.rb looks like as following

Here we can clearly isolate 3 entities:

  • The users resource
  • The posts resource
  • The admin namespace

So let’s see how to extract each of the entities in a separate file.

First, we have to manipulate our config/application.rb file

This line will redefine the target of our config.paths['config/routes.rb'].

The target is now all the Ruby files under the config/routes/ directory.

So when your application receives a request, Rails will search the expected route in the routing table defined under the config/routes directory.

Ok. So now, let’s have a look to the files in this directory

$>  $ tree config/routes
config/routes
├── base.rb
├── admin.rb
├── posts.rb
└── users.rb
0 directories, 4 files

Note that each file uses the Rails.application.routes.draw method to declare each resources.

Finally, you can remove the config/routes.rb file.

Conclusion

With this implementation, As things progress, you’ll be able to maintain your routes in a very easy way.

And that will be a good introduction to your app for new developers.

Voilà!


How to keep your routes clean in Ruby on Rails? was originally published in rubycademy on Medium, where people are continuing the conversation by highlighting and responding to this story.


The Life of a Radar

ROM and Dry Showcase: Part 4

This is the 4th (and final) part of a 4 part series covering the rom-rb and dry-rb suites of gems.

In this last part, we're going to make it so that our application can receive and respond to HTTP requests.

So far, we've been seeing how to use gems either from the dry-rb suite of gems, or the rom-rb suite of gems. In this part though, we're going to be using a gem from a different suite, a suite called Hanami.

Hanami is first and foremost a web framework. It has routes, controllers, actions, views and models. Just like your other favourite web framework -- Rails. But the big difference between Hanami and Rails is that with Hanami we can pick and choose the parts of the framework that we want to use.

Hanami is a modular web framework, and if all we want to use is the router, controllers and actions, then we can. In this part of this showcase, we're going to look at how we can use two gems from Hanami called hanami-router and hanami-controller. These gems will provide us with the features our application needs to receive and respond to HTTP requests.

Our application will take a request to POST /users with a JSON body shaped like this:

{
  "first_name": "Ryan",
  "last_name": "Bigg",
  "age": 32,
}

And the response will indicate if the request was successful or not. If it was successful, we will see a JSON response:

{
  "id": 1,
  "first_name": "Ryan",
  "last_name": "Bigg",
  "age": 32
}

Let's go!

Installing the Hanami gems

Let's add these gems to our Gemfile:

gem "hanami-controller", "~> 1.3"
gem "hanami-router", "~> 1.3"

To install these gems, we can run bundle install.

In order to make sure that whatever we build with this application is performing correctly, we'll add a third gem called rack-test:

gem "rack-test"

We're going to be using this gem to test our application in conjunction with RSpec. You might be thinking: why rack-test? That's because the part of our application that will recieve and respond to HTTP requests will be a Rack application! Just like every other Ruby web framework out there.

Setting up the test environment

Before we can write our any code, we need to write tests for it. It'll make sure that our application is working correctly! But before we can write tests, there's a bit of setup we need to do first. We're going to create a new file called spec/web_helper.rb. This file will setup how our tests can speak to our Rack application:

require "spec_helper"
require "rack/test"

module RequestHelpers
  def app
    Bix::Web.app
  end

  def post_json(path, data)
    post path, data.to_json, "CONTENT_TYPE" => "application/json"
  end

  def parsed_body
    JSON.parse(last_response.body)
  end
end

RSpec.configure do |config|
  config.define_derived_metadata(file_path: %r{/spec/requests/}) do |metadata|
    metadata[:request] = true
  end

  config.include Rack::Test::Methods, request: true
  config.include RequestHelpers, request: true
end

We set the web metadata flag on any tests that will go in spec/requests. This allows us to specify that the Rack::Test::Methods and RequestHelpers modules are included only into tests under that particular directory.

The Rack::Test::Methods module will include methods that we can use to make requests to our app, like get, post and so on.

The RequestHelpers module defines one method so far, called app. This app method is what the rack-test gem uses to know what application to talk to when we use those get / post / etc. methods.

We've defined the app value here to be a small Rack application that compiles a few parts. The first is Hanami::Middleware::BodyParser. This is a piece of middleware, that will convert our JSON input into parameters that our controller can access. The second part is Bix::Web::Router, which will be the main Ruby entrypoint for our application. That doesn't exist right now, but we'll create it in a moment. We'll see both of these parts again a little later on again.

The post_json method in RequestHelpers will allow us to make a POST request to our application and to send through JSON data with that request. Remember: the web part of application here is going to take JSON as input during a request, and it will also return JSON in a response. The parsed_body method will give us a Ruby hash of the response's body, and we can use this later on to assert the returned data is what we expect.

Before we create our router and all of the other parts, let's write a couple of simple tests to make sure it will behave as we wish.

Writing our first test

Currently, our application has a single transaction for creating users. We're going to use this transaction very soon, using it when a request to POST /users is made. We're going to add two tets now. These two tests will ensure that the application behaves correctly for valid and invalid input to POST /users. Let's add these new tests to spec/requests/users_spec.rb:

require "web_helper"

RSpec.describe "/users" do
  context "POST /" do
    context "with valid input" do
      let(:input) do
        {
          first_name: "Ryan",
          last_name: "Bigg",
          age: 32,
        }
      end

      it "succeeds" do
        post_json "/users", input
        expect(last_response.status).to eq(200)
        user = parsed_body
        expect(user["id"]).not_to be_nil
        expect(user["first_name"]).to eq("Ryan")
        expect(user["last_name"]).to eq("Bigg")
        expect(user["age"]).to eq(32)
      end
    end

    context "with invalid input" do
      let(:input) do
        {
          last_name: "Bigg",
          age: 32,
        }
      end

      it "returns an error" do
        post_json "/users", input
        expect(last_response.status).to eq(422)
        user = parsed_body
        expect(user["errors"]["first_name"]).to include("is missing")
      end
    end
  end
end

These tests should look pretty familiar! They are essentially the same tests for our transaction, just with rack-test methods being the primary difference.

When we attempt to run these tests, we'll see that we're missing a part of our application:

  1) /users POST / with valid input succeeds
     Failure/Error: Bix::Web.app

     NoMethodError:
       undefined method `app' for Bix::Web:Module

Oh right! We need to setup this Web thing!

Building the Web component

To setup this web part of our application, we're going to add a new file to system/boot, called web.rb. In this file, we'll need to require all the gems that we'll be using for the web part of our application:

Bix::Application.boot(:web) do |app|
  init do
    require "hanami-router"
    require "hanami-controller"
  end
end

This two lines will require the hanami gems that we're going to be using here. Where we'll use these gems is in a couple of files.

The first is a file called lib/bix/web/application.rb. This is where we'll define the different Rack pieces for our application:

require "hanami/middleware/body_parser"

module Bix
  module Web
    def self.app
      Rack::Builder.new do
        use Hanami::Middleware::BodyParser, :json
        run Bix::Web::Router
      end
    end
  end
end

This file is defines the Bix::Web.app method that our test is looking for! This method returns a Rack::Builder object, which is to say it returns a Rack application.

This Rack application uses a single piece of middleware: Hanami::Middleware::BodyParser. This middleware is used to take in any JSON request body, and to transform it into parameters for our actions.

The run line at the of the builder's block directs Rack to the application that will be serving our requests. Let's build this part now in lib/bix/web/router.rb:

module Bix
  module Web
    Router = Hanami::Router.new do
      post "/users", to: Controllers::Users::Create
    end
  end
end

This file allows us to define routes for the web side of our application. This route defines a POST /users request to go to Controllers::Users::Create. What is this mythical constant? It's going to be the action that serves this request.

In this application, we're going to put actions inside their own classes. This will keep the code for each action more clearly isolated from other actions.

We'll define this action inside lib/bix/web/controllers/users/create.rb:

module Bix
  module Web
    module Controllers
      module Users
        class Create
          include Hanami::Action

          def call(params)
            self.body = "{}"
          end
        end
      end
    end
  end
end

This action class includes the Hanami::Action module from the hanami-controller gem. This gives us access to a number of helpful methods, but the only one of these we're using now is self.body=, which we're using to set the response body to an empty JSON hash. What's also worth mentioning here is that due to us not specifying a status, this action will return a 200 status.

With our router and controller now setup correctly, let's switch back to looking at our tests.

Running our tests

When we run these tests with bundle exec rspec spec/requests we'll see they're both failing:

  1) /users POST / with valid input succeeds
     Failure/Error: expect(user["id"]).not_to be_nil

       expected: not nil
            got: nil
     # ./spec/requests/users_spec.rb:18:in `block (4 levels) in <top (required)>'

  2) /users POST / with invalid input returns an error
    Failure/Error: expect(last_response.status).to eq(422)

      expected: 422
          got: 200

      (compared using ==)
    # ./spec/requests/users_spec.rb:36:in `block (4 levels) in <top (required)>'

This is happening because all our action returns is an empty JSON body. Let's work on changing this.

We'll change the action to use the transaction class:

module Bix
  module Web
    module Controllers
      module Users
        class Create
          include Hanami::Action
          include Import["transactions.users.create_user"]
          include Dry::Monads[:result]

          def call(params)
            case create_user.call(params.to_h)
            in Success(result)
              self.body = result.to_h.to_json
              self.status = 200
            in Failure(result)
              self.body = { errors: result.errors.to_h }.to_json
              self.status = 422
            end
          end
        end
      end
    end
  end
end

At the top of this controller action, we import the create_user transaction by using the Import constant that we made a few parts ago -- this is from dry-auto_inject.

Then we include Dry::Moands[:result] -- this gives us access to the Success and Failure methods we use inside the action.

Inside the action itself, we call the transaction and then use Ruby 2.7's new pattern matching to decide what to do. In the case of a successful transaction, we return the body of the result. If it fails, we return the errors and set the status to 422.

This should be exactly what our test is expecting. Let's run them again and find out:

2 examples, 0 failures

Good! Our tests for our router are now passing. But this only means that our router is working, not that we can serve HTTP requests yet! We need one final piece for that to work.

Racking up the server

To run our HTTP server, we'll use a gem called puma. Let's add that gem to the Gemfile now:

gem "puma"

And we'll run bundle install to install it.

To run the Puma server, we can use the command by the same name:

puma

When we do this, we get an error:

Puma starting in single mode...
* Version 3.12.1 (ruby 2.7.0-p0), codename: Llamas in Pajamas
* Min threads: 0, max threads: 16
* Environment: development
ERROR: No application configured, nothing to run

This is because Puma hasn't been told what to run yet. The good thing for us is that Puma will look for a special file to know what to run. That file is called config.ru. Let's create that file now:

require_relative "config/application"

Bix::Application.finalize!

run Bix::Web.app

This file looks a lot like bin/console:

#!/usr/bin/env ruby

require_relative '../config/application'

Bix::Application.finalize!

require 'irb'
IRB.start

The difference is that we're starting a server, instead of starting a console session.

Let's try puma again:

Puma starting in single mode...
* Version 3.12.1 (ruby 2.7.0-p0), codename: Llamas in Pajamas
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:9292

Great! We now have a HTTP server listening on port 9292.

To test this out, we can do one of two things. If you have the marvellous httpie installed, you can run this command:

http --json post http://localhost:9292/users first_name=Ryan last_name=Bigg

Otherwise, if you're using curl, it's a little more verbose:

curl --request 'POST' \
-i \
--header 'Content-Type: application/json' \
--data '{"first_name":"Ryan"}' \
'http://localhost:9292/users'

(Use HTTPie!)

Either way, what we'll see returned here is a validation error message indicating that our input was not quite complete:

HTTP/1.1 422 Unprocessable Entity
Content-Length: 39
Content-Type: application/json; charset=utf-8

{
    "errors": {
        "last_name": [
            "is missing"
        ]
    }
}

Note here that the HTTP status is 422 as well.

Great, so that means the failure case for our action is now working as we wished it would.

Let's see if we can test out the success case too with this http call:

http --json post http://localhost:9292/users first_name=Ryan last_name=Bigg

Or this curl one:

curl --request 'POST' \
-i \
--header 'Content-Type: application/json' \
--data '{"first_name":"Ryan", "last_name": "Bigg"}' \
'http://localhost:9292/users'

Now we will see a successful response:

HTTP/1.1 200 OK
Content-Length: 140
Content-Type: application/json; charset=utf-8

{
    "age": null,
    "created_at": "[timestamp]",
    "first_name": "Ryan",
    "id": 6,
    "last_name": "Bigg",
     "updated_at": "[timestamp]"
}

And that's all now working!

Summary

In this fourth and final part of the ROM and Dry showcase, we barely looked at either Rom or Dry! Instead, we looked at some pieces of the Hanami web framework.

The Hanami web framework is a great alternative to the Rails framework that I've loved for a few years. What's been great about Hanami in this series is that we were able to opt-in to using Hanami's hanami-router and hanami-controller gems without having to opt-in to absolutely everything else from Hanami too.

These gems, along with the puma and rack gems, have allowed us to build a HTTP interface to our application. Our application is now capable of receiving and responding to HTTP requests.

I hope that this series has given you a great demonstration of what the rom-rb, dry-rb and Hanami gems are capable of. I strongly believe that these are viable, new-age alternatives to Rails for building modern Ruby applications.

I hope you continue to explore what these gems can offer and how you can approach building better, easier to maintain applications with them.

Wednesday, 19. February 2020

Fabio Akita (AkitaOnRails) @ São Paulo › Brazil

[Akitando] #69 - Respondendo suas Perguntas sobre Carreira | via Instagram

DESCRIPTION

Muita gente me procura via mensagens diretas em redes sociais, em particular pelo Instagram. Pro episódio de hoje eu selecionei 10 perguntas que representam a maior parte dos temas que costumam me perguntar e apesar de já ter parcialmente respondido algumas delas via os temas de outros vídeos passados, vou tentar compilar respostas um pouco melhores hoje. Espero que ajude.

O tema vai ser “Perguntas mais frequentes de iniciantes no mercado de tecnologia”. Diferente do vídeo que eu fiz chamado “Começando na Carreira de TI” (https://www.youtube.com/watch?v=H84WD_xyj10) onde eu abordei assuntos que começam mais na escolha da faculdade primeiro, esta é mais focado no primeiro emprego e na forma de começar a evoluir. Depois de assistir este video convido vocês a tentarem o vídeo “Dimensão do Tempo” (https://www.youtube.com/watch?v=Qb5b8ZE9tIY) e “O Mercado de TI para Iniciantes em Programação” (https://www.youtube.com/watch?v=O76ZfAIEukE) também.

Se vocês ainda não me seguem nas redes sociais, não deixem de seguir (@akitaonrails em todo lugar). E podem mandar perguntas em privado que eu possa usar em próximos episódios de perguntas e respostas.

Links diretos pra cada pergunta

03:14 - pergunta 1

11:27 - pergunta 2

15:23 - pergunta 3

19:23 - pergunta 4

21:30 - pergunta 5

29:00 - pergunta 6

30:30 - pergunta 7

33:13 - pergunta 8

35:14 - pergunta 9

36:21 - pergunta 10

SCRIPT

Olá pessoal, Fabio Akita

Ultimamente eu ando bem distraído por assuntos pessoais, e ao mesmo tempo tem alguns assuntos técnicos pros próximos episódios que vão demandar mais tempo de pesquisa do que eu normalmente faço, por isso acabei procrastinando um pouco mesmo. Provavelmente minha frequência de lançar videos vai ficar irregular agora entre dezembro e janeiro. Um desses assuntos pessoais é que estou pra me mudar pra outro apartamento, e ainda tem uma obra que vai começar agora, tem feriados que vão atrapalhar essa obra. Eu imagino que quando eu terminar de me mudar e estiver com tudo pronto mais do meio pro fim de Janeiro é que vou conseguir me focar 100% pro canal de novo. Inclusive, até o fim de Janeiro significa que o cenário do canal vai mudar também.

Pro episódio de hoje eu resolvi repescar algumas perguntas que vocês me mandaram em mensagem privada. Eu não peguei todas, acho que vasculhei um período de quase 2 meses de mensagens no Instagram. Por alguma razão a maioria das pessoas interage mais comigo no Instagram mais do que em qualquer outra rede social. Aliás, se vocês nunca prestaram atenção, todo vídeo eu começo com meus perfis de rede social, então não deixem de me seguir. Às vezes eu não consigo responder todas as perguntas como eu gostaria. Não se sintam mal quando eu não respondo, muitas vezes é simplesmente porque eu esqueço mesmo, outras vezes é porque eu não estou muito a vontade de responder qualquer coisa rápida.

A maioria das perguntas acaba sendo em torno de assuntos de carreira, principalmente início de carreira. Eu já falei sobre vários desses assuntos como no vídeo sobre Mercado de TI ou Conhecimentos Gerais e vários outros. No episódio de hoje eu vou repetir algumas das coisas que já falei em outros vídeos, mas vou tentar reformular em algo que se pareça mais com respostas. Então vamos lá!

Antes de mais nada, eu não vou dizer quem mandou as perguntas. Eu imagino que se me perguntaram em privado pode ser que não se sintam a vontade de eu expor publicamente. Então se vocês reconhecerem suas perguntas e quiserem expandir em cima delas nos comentários, sintam-se à vontade. Ou se preferem falar em privado, podem mandar direct no Instagram ou Twitter.

Outra coisa que é meio óbvio mas melhor avisar antes. Cada pessoa tem circunstâncias diferentes. Por isso não existe resposta universal que cabe pra todo mundo. Na minha posição, toda resposta podia começar com "depende". Depende de que tipo de formação você já teve, depende de quanto você tem condições de investir em educação, depende se você tem objetivos específicos como morar fora do país, depende se você tem limitações que te impedem de escolher um ou outro caminho. Por isso ajuda muito se você se apresentar um pouco melhor quando faz as perguntas.

E isso já é uma dica: no trabalho muita gente quando fala alguma coisa acha que todo mundo devia entender suas circunstâncias, mas ninguém vai saber se você não disser. Todo mundo vai responder no seu ponto de vista particular e não no de quem perguntou. Por causa disso também, minhas respostas provavelmente vão ficar meio compridas. Porque eu vou tentar dar um contexto em vez de só dar uma receita simples. Então vamos começar com a primeira pergunta que é: "Quais dicas você daria pra quem tá começando na área de dev. Interior, quase não tem empresas pra contratar devs, só com faculdade."

Eu achei essa pergunta interessante porque na real é onde eu acho que a maioria das pessoas se encaixa no Brasil. Muito do material sobre carreira parece mais voltado pra quem é de algum grande centro ou pólo tecnológico como São Paulo, Belo Horizonte, Porto Alegre, Recife. Mas a maioria das pessoas vive fora das capitais. Durante mais de uma década eu visitei cidades como Xanxerê, Maringá, Rio Branco, Criciúma, Macapá, Campos de Goytacazes. A maioria sequer tem aeroportos perto. A indústria de tecnologia na maioria das cidades do interior simplesmente não existe.

Pouco a pouco isso vem mudando, muitas empresas estão procurando cidades fora dos grandes centros. A minha própria empresa, a Codeminer, costuma procurar cidades fora de São Paulo pra montar escritório, por isso estou em lugares como Santa Maria, Guarapuava, Poços de Caldas, mas nenhum de nós vai conseguir cobrir o Brasil inteiro tão cedo. Muitos estudantes tem o sonho de se mudar pra uma cidade maior e alguns até mesmo morar em outro país, mas isso não é realidade pra muita gente que não quer ou mesmo não pode deixar a família pra trás, por exemplo.

Por isso muita coisa que se usa em tech startups, linguagens mais arrojadas como Elixir, Clojure, Scala, Go, Typescript ou até mesmo coisas que consideramos super normal como Lambdas, NoSQL, Machine Learning, nada disso é remotamente perto da realidade. No máximo tem projetinhos Web simples que se integram com sistemas legados que tem 20, 30 anos de idade até. Pra todos os efeitos e propósitos, não é exagero eu dizer que já vi empresas do interior que estão literalmente presos a tecnologias e práticas que eu só via nos anos 90 e começo dos anos 2000. Você vai achar implementações de ERP como os da Totus, ou coisas customizadas dessa época feita em Delphi antigo, ou até mesmo Clipper, que eu parei de usar em 1994.

Então vamos lá. Vamos assumir que por qualquer razão, você esteja preso na região. Não tem planos de se mudar tão cedo. Não há sequer agências pequenas de sites e serviços de Web. Só empresas que Não são tecnologicamente avançadas mas tem sistemas pra manter. Coisas em Java antigo, ou .NET antigo, ou VB ou Delphi ou até Clipper. Que usam bancos de dados antigos como Interbase, Informix, Ingres, Caché. Se você não tem opção, então a escolha já está feita. Eu imagino que você vai precisar comer e pagar suas contas. E se sua vocação é programação, não tem como evitar lidar com tecnologias antigas.

Começar em empresas assim, ao contrário do que pode parecer, não é necessariamente uma coisa ruim, especialmente se você está no começo de carreira. Mas tem duas coisas que você vai precisar fazer. A mais importante delas é estudar inglês até se tornar fluente. Esse vai ser seu passaporte pra conseguir coisas melhores. Eu fico pasmo que até hoje ainda tem gente na área que desincentiva o aprendizado de inglês mas fica empurrando tecnologias inúteis que a maioria das empresas sequer tem intenções de usar por muito tempo ainda. A menos que você goste de programação só como hobby, você precisa aprender inglês, o quanto antes. Pra saber como fazer isso sem gastar muito, veja meu video sobre Como eu aprendi inglês.

Apesar de hoje de fato termos bons materiais em português pras tecnologias mais populares, a maioria delas fica defasada muito rápido, porque todas as linguagens, frameworks, ferramentas e plataformas, estão recebendo novas versões o tempo todo. E quanto mais usado é uma tecnologia, mais rápido ele recebe atualizações. Em poucos meses um livro em português fica defasado. Mesmo em inglês os livros ficam defasados apesar deles receberem novas edições com muito mais frequência do que no Brasil. Por isso a grande maioria dos programadores que estão à frente não estão estudando por livros, mas sim pelas documentações nos sites oficiais de cada tecnologia e foruns de discussão, e adivinhem: 100% delas é em inglês. As tecnologias que não são populares ainda, mas que tem potencial de mercado no futuro próximo, sequer tem livros publicados ainda.

Pior ainda, talvez muitos nem saibam disso mas se você usa Wikipedia em português, você sempre está lendo versões curtas e incompletas. Vejam por exemplo o verbete de computação quântica que foi assunto que eu gravei a respeito algumas semanas atrás. Em inglês tem mais de 70 referências. Em português tem uma lista ridícula de 18 links, metade pra posts de blog. O mesmo vale pra stackoverflow que todo programador usa todo dia. Se você não usa a versão em inglês, você está perdendo provavelmente 80% das opções, se não mais. Nem importa se você ainda não é fluente, pare de usar as versões em português e vá direto na versão mais completa em inglês. Por que você quer ficar em desvantagem em relação a todo mundo?

Enquanto seu inglês for ruim, sim, use todo material em português que conseguir encontrar porque não tem outra opção, mas faça isso sabendo que você está com um material que é incompleto ou já está defasado. Agora, sua vida vai ser bem mais difícil mesmo. Você vai ter que aprender a tecnologia antiga que a empresa da sua região usa e em paralelo vai ter que estudar as tecnologias um pouco mais modernas que são mais populares no mercado dos grandes centros, como Javascript ou Python, frameworks como React, linguagens mais modernas como Go. Se as empresas da sua região pelo menos usarem coisas como Java ou .NET menos mal, porque elas são fundação pra muita coisa nova. Eu diria que todo programador deveria saber um pouco ou de Java ou mesmo C#. Você aprecia melhor as novas linguagens se souber algumas um pouco mais antigas. Não procure a linguagem que é a mais simples pra aprender, ou a que você acha que pagam melhor, se for você realmente entrar na carreira de programação, você vai aprender várias linguagens diferentes ao longo do tempo. Se você estiver anos com a mesma, você está se colocando em desvantagem, de novo.

O outro problema é a falta de faculdade. Nos grandes centros isso também ainda é um requisito, especialmente em começo de carreira onde você ainda não tem muito como demonstrar o que sabe, porque não tem experiência anterior. Não preciso dizer que as leis trabalhistas do Brasil são uma das piores do mundo, sério é o conjunto de leis mais primitivo e imbecil e um das coisas que mais dificulta a entrada de iniciantes no mercado. Por exemplo, você só pode ser estagiário se estiver cursando faculdade, porque estágio é um processo assinado entre empresa, o estagiário e a faculdade. Existe a obrigatoriedade da figura da instituição de ensino. Pra contratar alguém, sem faculdade e sem experiência, é um custo muito alto pra maioria das empresas, porque o vínculo empregatício e a liability que isso trás custa caro. As leis trabalhistas são obstáculos pra todo mundo que não fez faculdade e precisa buscar experiência. Essas leis garantem que você que não fez faculdade comece em desvantagem.

Daí a única outra alternativa que pode acabar sobrando é tentar se tornar autônomo ou freelancer muito cedo na carreira. E isso é péssimo, porque se você aprendeu sozinho e nunca trabalhou em projetos de verdade ou com alguém mais experiente que consiga te mentorar, você não tem noção ainda do que um projeto realmente precisa pra ser entregue de ponta a ponta, e vai ter um caminho ainda mais difícil. Especialmente porque sem saber até onde dá pra ir, você só vai ficar repetindo as poucas coisas que aprendeu, e muitas vezes do jeito errado por muito tempo. A maioria desiste porque a pressão e frustração são grandes demais.

Por isso eu digo que mesmo se na região não tiver empresas com tecnologias modernas, mesmo que não paguem tão bem, ainda assim é melhor que nada. Você precisa ver como uma empresa funciona no mundo real. Mesmo se ela for ruim, você precisa ver o que ser ruim significa. Mesmo se o código for antigo e mal feito, você precisa ver como é um código antigo e mal feito. E ao mesmo tempo aprender como lidar com outras pessoas, mesmo chefes ruins pra saber o que significa ser um chefe ruim. Entenda, mesmo com faculdade, tendo aprendido só teoria, por mais que tenha lido livros e feito cursos online, você ainda não sabe 1% do que precisa.

Pra continuar, vou aproveitar o gancho da segunda pergunta que separei pra hoje que diz: "É legítimo o alarmismo com relação a escassez de profissionais na área de programação? Mão de obra qualificada. Não que precise ser genial, mas alguém que tenha uma base boa pelo menos."

E sim, existe escassez de profissionais qualificados neste mercado. Não é alarmismo. Nos grandes centros a situação é bem diferente do interior e regiões afastadas do país. São realidades completamente diferentes aliás. Em centros como São Paulo, Rio de Janeiro, Recife, Belo Horizonte, Porto Alegre, e um pouco menos em outras capitais como Florianópolis, Curitiba, Fortaleza, por exemplo, o ciclo econômico dos últimos 10 anos trouxe um boom de tech startups e grandes empresas que absorveram parte da cultura das tech startups. Essas regiões atraíram muito investimento tanto nacional quanto internacional. Não é exagero dizer que o Brasil em 10 anos conseguiu crescer um ecossistema de tech startups muito grande. Só que com todo crescimento rápido demais conseguir mão de obra qualificada vai ficando mais e mais difícil. Porque qualificação de verdade não acontece do dia pra noite, exige anos de dedicação, estudo e experiência.

Pra tentar suprir essa demanda, também houve uma explosão em cursos online e geração de material de aprendizado. Mas não existe milagre. Educação exige tempo. É impossível só via um curso online produzir programadores seniors, nem mesmo pleno. Via de regra, qualquer curso online, os melhores, só vão conseguir gerar, no máximo do máximo, programadores júniors. A partir de um determinado ponto, o júnior precisa obrigatoriamente se inserir numa empresa de verdade pra aprender na prática o que significa desenvolver um sistema grande de verdade, como desenvolvedores mais experientes resolvem problemas de verdade. Nenhum curso jamais vai conseguir queimar esses passos. Aliás, se você ver algum curso ou bootcamp que faça promessas imbecis como "estude com a gente e pule de júnior pra pleno em x tempos" eles são charlatães, fuja. Outros cursos exigem que você já seja um pleno pelo menos e tenha uma formação boa pra conseguir usar o material mais avançado.

O lado menos óbvio se você não é da área é que apesar do número de tech startups ou empresas similares em tecnologia tenha crescido rápido, elas tem um problema grave: quanto mais investimentos ela recebe mais a pressão pra crescer mais rápido. Só que a maioria não tem experiência em gerir uma empresa que cresce tão rápido. E dentre as várias coisas que elas são péssimas em fazer é treinar os programadores júniors. Por isso elas tendem a procurar só programadores mais experientes, muitas vezes tentando tirar funcionários de outras empresas e pagando mais caro do que deveriam. Isso causa distorções graves no mercado e ao mesmo tempo é um tiro no próprio pé. Elas não percebem isso porque muitas sequer dão lucro, vivem de rodada de investimento atrás de rodada de investimento, então eles não entendem o conceito de eficiência, são máquinas de queimar dinheiro. A maioria vai quebrar ou ser adquirida por outra empresa maior.

Então de um lado existe alta demanda e escassez de profissionais experientes. E do outro lado existe uma oferta crescente de programadores júniors saindo de cursos online que não vão conseguir chegar nesse nível de qualificação sendo contratados por tech startups que ainda não chegaram no nível de crescimento acelerado via investimentos. E esse é aquele cenário onde o júnior é contratado como se fosse sênior, não vai ser mentorado direito, e tem grandes chances de acabar queimado, frustrado e sem evolução adequada. Por isso outra consequência é que o nível de retenção é baixo, os programadores crescendo nesses ambientes tem uma visão irreal de como as coisas funcionam e do que realmente valem, e a barreira de entrada é maior. Portanto existe essa escassez de moscas brancas, os seniors que conseguem carregar esse tipo de estrutura ineficiente nas costas.

Pra completar esse pensamento, vou engatar a terceira pergunta que é "Você falou sobre aprender tecnologias que estão ficando "top trending", mas seria errado você aprender uma tecnologia já bem estruturada no mercado de trabalho e depois se aventurar por tecnologias novas?"

Parte da estratégia das tech startups é tentar adotar tecnologias mais novas que elas acreditam que podem dar alguma vantagem no crescimento delas. São as que estão usando os Clojure ou Elixir da vida, coisas mais novas da AWS como Lambdas, serviços de machine learning de uma Azure por exemplo. Ao mesmo tempo é uma forma de tentar reter as pessoas porque não tem tantos outros lugares que elas podem usar tecnologias novas demais. Por outro lado você é um profissional que tem mais valor se ao mesmo tempo souber as coisas novas e tiver domínio sobre outras tecnologias mais difundidas e maduras como Java ou Python por exemplo.

Uma empresa de tecnologia raramente usa uma única marca de tecnologias, porque sempre existe a ferramenta certa pro uso certo. Saindo do mundo Web, você tem mais valor como programador mobile se souber Java e Kotlin direito pra programar Android. Ou pra mercados mais de nicho que faz coisas como IoT ou hardware embarcado. Ou mesmo em grandes corporações. Aliás, voltando à conversa de se você for iniciante e tiver acesso a uma região geográfica com mais grandes empresas que não são tech startups, essas podem ter condições de oferecer programas de trainée mais estáveis, que conseguem absorver mais programadores júniors inexperientes e dar um treinamento melhor. Eu expliquei um pouco mais sobre isso tanto no meu vídeo sobre o Mercado de TI quanto no de SAP, não deixem de assistir pra saber mais.

Quando se fala em carreira você não deve ser imediatista. Eu sei como é, a gente é jovem, vê o brilho das tech startup, tecnologias modernas, e acha que esse é o objetivo do emprego dos sonhos. Baixe suas expectativas. Tech startups no geral são péssimos lugares pra iniciantes, porque a prioridade não é te ensinar nada, a prioridade é crescimento acelerado acima de tudo, muitas vezes queimando etapas. É um lugar melhor pro senior aventureiro que tem mais mobilidade pra fazer o que ele já sabe que tem que ser feito. Mas pra maioria dos juniors, você pode acabar sendo queimado rápido demais e se frustrar com a área antes do tempo. De novo, óbvio que tem exceções, mas como estou considerando a maioria da população e o mercado em geral, eu diria pra procurar empresas que tem um histórico maior, já existem faz 10 anos ou mais, e que oferecem um mínimo de suporte pra ganhar experiência. Não existem garantias, só brincar com a sorte mesmo. Talvez você desperdice menos tempo no começo, que é quando você ainda não vai conseguir enxergar tudo. Mesmo que essa empresa seja como no caso da primeira pergunta: no interior, com tecnologias legadas.

Muito se fala em soft-skills. Tem milhares de podcasts, videos, livros e palestras sobre o assunto. O mais importante é aprender a ser honesto. E não, se você é iniciante, você ainda não sabe disso. Quando você trabalha sob pressão é que você vai saber se é de verdade ou se também vai sucumbir a dar respostas fáceis. Ser honesto é saber dizer “não” quando sabe que não vai conseguir entregar. Só que não dá pra dizer “não” sempre, senão pra que você serve? Como aprender a dizer esse “não” na hora certa, ou melhor, como aprender a gerenciar expectativas?

Você pode ler quantos livros quiser. Mas a única forma de aprender é na prática, em situações reais, tomando até decisões erradas e tendo que consertar depois. Por isso eu digo que não importa se é uma empresa de interior, com software mal feito, com chefes ruins. Se você sempre for o cara que diz sim, fica quieto quanto devia falar, ou fala demais quando não precisa, e não consegue explicar objetivamente porque você prefere uma opção que considera melhor do que a outra que a empresa já usa, então não importa se é uma empresa de interior ou uma tech startup, você ainda não tem o que precisa. Então vai lá e pratica.

Agora aproveito pra colocar a quarta pergunta: "Iniciei recentemente um curso de Javascript e agora estou estudando o mesmo por conta. Gostaria de saber, apenas com Javascript consigo algum trabalho, o quanto tenho que saber nessa linguagem pra atuar na área? Qual é o mínimo que preciso saber pra atuar em uma empresa ou prestação de serviços?"

Isso é um pouco o que eu já respondi acima. Neste momento existe uma demanda alta também por bons profissionais de Javascript. Mas não basta só saber um pouco da linguagem, isso é simples. No caso de front-end, você precisa saber frameworks como React, precisa saber Redux, dependendo da região geográfica precisa saber os concorrentes como Angular que infelizmente muitas empresas ainda usam. Precisa saber como organizar projetos corretamente, as melhores práticas pra organizar módulos, como configurar coisas com Webpack direito.

Precisa saber as bibliotecas mais usadas. Se for fazer uma landing page ou gerenciador de conteúdo precisa saber coisas como Gatsby. Se for server-side precisa saber Node, precisa saber Express. Precisa saber usar frameworks de teste como Chai, Mocha, Puppeteer. E isso que acabei de falar aqui é só o básico. É o mínimo pra Javascript. E quando falamos Javascript, claro, estamos falando da versão ES6 que é a mais usada hoje, mas lembrem-se do que falei no começo: muitos livros e documentações antigas estão defasadas na versão ES5. A forma como se organiza código Javascript hoje e 5 anos atrás é bem diferente.

Como falei na pergunta anterior, por causa desse boom de crescimento rápido tanto de tech startups como produtos web em grandes empresas, a demanda por profissionais que conheçam coisas como Javascript cresceu bastante. Especialmente porque muitos modernizaram ou estão modernizando os front-end com componentes React, usando coisas como Progressive Web Apps pra fazer aplicativos web que funcionem como aplicativos desktop, como a app do Twitter por exemplo. Javascript completo hoje é muito mais abrangente do que 5 ou 10 anos atrás.

Eu diria que hoje, indo pra 2020, o ecossistema Javascript tá um pouco mais estável e menos caótico do que 3 a 5 anos atrás que era uma zona completa. Mas agora se você é iniciante e não sabia disso, pode estar meio assustado, como aprender tanta coisa logo no começo? Vamos pra quinta pergunta que é gancho pra isso, e a pessoa falou: "Tenho alguns problemas que me impedem de prosseguir nessa carreira. Sou muito imediatista, quando inicio algum curso, seja de inglês ou de uma linguagem nova, eu já penso em um longo caminho pra percorrer e acabo desanimando de prosseguir, não consigo adquirir uma rotina de estudos. E também tenho medo de ser burro e não saber programar, ou esquecer de tudo na hora que uma empresa me contratar. Meu conhecimento na teoria é bom, mas na hora de praticar ou criar alguma coisa, eu esqueço ou desanimo por pensar que não consigo achar o que as pessoas iriam pensar. Sempre me dá um branco e não consigo começar nada, nem um portfolio que preciso muito. Tem alguma dica? Já passou por isso?"

Eu já estou parecendo uma vitrola quebrada, mas vou repetir isso até vocês enjoarem ou entenderem de uma vez: existe um limite do que cursos e livros podem fazer só na teoria. Chega uma hora que simplesmente não adianta só estudar. Ninguém aprende a programar só lendo. Não tem um ponto exato pra começar, mas eu sempre vou dizer que você precisa dividir seu tempo entre estudar teoria e praticar. O problema é que muito iniciante ainda acha que prática vem no emprego. E isso era bem verdade, até o começo dos anos 2010 pelo menos. Mas especialmente nos últimos 10 anos com um acesso melhor que a maioria da população tem de internet de banda larga e com a popularização de plataformas como GitHub, você tem literalmente milhares de projetos open source à sua disposição.

Eu falei no episódio sobre a Dor de Aprender o que você tem que fazer: não fique só lendo post, vá no GitHub e baixe os projetos. Digamos que você é o iniciante no interior, que só tem a opção de trabalhar numa empresa com sistemas legados. Ou mesmo se você é um júnior numa tech startup caótica que ninguém tem tempo nem estrutura pra te treinar. Como você vai conseguir aprender o que é um bom código se você só está exposto a código mal feito, seja porque é legado ou seja porque é feito às pressas num lugar que prioriza crescer rápido acima de tudo?

A sua única opção é código open source. Não estou dizendo que todo projeto open source tem bom código, na verdade tem muito código bem porcaria também. Mas o importante é que você consegue achar bons códigos. E mais do que isso. Eu falei que você precisa saber um monte de bibliotecas que muitas empresas modernas usam. Muitas dessas bibliotecas sequer tem cursos ou livros. A sua única forma de saber como elas são usadas é procurar projetos que usam essas bibliotecas. Exemplo simplificado em post de blog é muito pouco. Mas ver só num projeto é pouco, você precisa ver como vários projetos usam a mesma biblioteca de maneiras diferentes.

Aqui você vai encontrar a barreira: você vai ter que encarar qual é sua real determinação em querer aprender. Se você diz que só consegue estudar dentro do horário de uma aula de curso. Se você diz que só consegue pegar no teclado pra digitar código durante o horário de serviço. Essa é sua barreira. Não é um defeito, só não é sua vocação. A maioria dos programadores é assim na realidade. São mais raros os que tem a determinação de, nas horas vagas, ler documentação online, baixar as dezenas de projetos na própria máquina e treinar esses códigos.

Lembre-se, você não tem experiência, muitos que não quiseram ou não puderam fazer faculdade, sequer tem a fundação. Se você não tiver essa disposição, eu realmente não tenho muito mais que eu posso recomendar pra você. Literalmente todo dia tem coisa nova aparecendo. Você pode escolher que não quer ter que estudar pro resto da vida, então você tem a opção de empresas que não estão tão preocupadas em seguir as novas tendências. As tais empresas de interior, empresas cujo foco não é tanto tecnologia, setor público por exemplo. Eu chutaria que a maioria dos programadores na realidade são mais codificadores, não estão tão preocupados em buscar as melhores soluções, mais em manter o que já tem e torcer pra não mudar muito pra não ter que estudar nada novo. Já deixo bem claro que esse não é meu público alvo.

Mas se você gostaria de uma carreira que vai evoluir, e você vai ter mais valor no futuro, você tem que colocar na cabeça que vai ter que estudar e praticar com frequência, pro resto da sua vida, não é uma opção. Na pergunta a pessoa fala que tem dificuldade de se colocar numa rotina de estudos. Isso é muito comum. Eu também não consigo ficar estudando coisas na teoria sem nunca usar na prática. Eu prefiro iniciar pela prática e buscar a teoria pra complementar o que eu não sei e vou intercalando. Raramente eu leio um livro técnico do começo ao fim. Na verdade eu não lembro se um dia cheguei a fazer isso. Eu realmente nunca li um livro técnico de cabo a rabo. Seria uma enorme perda de tempo. Livro técnico é que nem dicionário: é uma referência que você busca em momentos específicos, pra ler um trecho específico, e daí voltar pra prática.

E aí tem o tal medo de numa entrevista não lembrar do que estudou. Ou mesmo quando vai tentar começar um projeto não se lembra também. A razão disso é a falta de prática. Só fazer curso e ler livro é uma chatice. Eu não lembro a última vez que fiz algum curso, mesmo lá no começo. Na real, faz anos que eu não leio livros técnicos nem faço cursos online. Eu só procuro referências quando eu tenho dúvidas específicas. Como eu disse, pra muitas das coisas novas, sequer existem cursos. Eu prefiro sempre ir atrás das documentações online e em alguns casos abrir o código fonte dos projetos que quero usar pra ter uma noção de como funcionam.

Muita gente recomenda você começar um projeto pessoal do zero. É a pior recomendação que se pode dar, justamente porque se você não tem experiência, como vai imaginar como se começa um projeto do zero? Na verdade isso é uma experiência super frustrante pra quem tem zero experiência. É muito melhor começar baixando projetos dos outros. Copie código dos outros, sem nenhum objetivo de no final ter um produto seu que tenha alguma utilidade.

Eu disse isso em outro vídeo e vou repetir de novo: copie muito código. Literalmente abra o navegador no projeto do Github de um lado, abra seu editor de textos do outro, e copie. Treine sua memória muscular a se acostumar com código. Não só baixe o projeto e rode. Crie um novo diretório e redigite tudo do zero de novo. Você vai descobrir que mesmo copiando, seu código vai falhar. Você vai descobrir que às vezes é melhor codar algumas coisas do começo ao fim, às vezes é mais fácil copiar uma parte, tentar rodar, depois continuar. Você vai descobrir que algumas partes você já sabe como codar de uma forma melhor e vai ver na prática se o que você acha que é melhor realmente funciona. E além disso vai ver bibliotecas sendo usadas que você nem sabia que existiam e pra que servem, na prática. Quando você está começando do zero, isso é extremamente importante.

Nem pense tanto em contribuir nos projetos open source logo de imediato. Essa não é a prioridade. Com o tempo, você vai saber quando tá apto a contribuir. E contribuir é vantajoso tanto como exercício e prática quanto pra montar portfolio e currículo. Porque é um código seu que qualquer um consegue avaliar e ver até onde você consegue contribuir. No Brasil parece que nem toda empresa olha isso, mas no exterior é um critério muito importante.

A sexta pergunta eu ouço bastante, a pergunta é curta e a resposta não vai ser satisfatória, mas a partir daqui minhas respostas devem ficar um pouco mais curtas pelo menos. Vamos lá, a pergunta é assim: “Qual seria a linguagem mais fácil pra começar a programar? Eu tentei Python mas falhei.”

Cara, Python é uma das linguagens mais simples pra começar. A sintaxe dela é uma das mais simples, tem poucas exceções às regras, ninguém fica inventando muita moda nos patterns. Não é a toa que muitas faculdades escolhem Python hoje, porque realmente é simples de começar. Mas isso dito, não existe isso de “linguagem mais fácil pra começar”. Qualquer linguagem é fácil pra começar. É que nem eu perguntar: qual idioma é mais fácil pra começar. Tanto faz. Qualquer criança no mundo, nascida em qualquer país do mundo, aprende o idioma local. O que faz a diferença não é a dificuldade do idioma comparado com a que você já sabe, é quanto você está disposto a mergulhar nela. Como eu disse antes, se sua disposição for fraca, qualquer opção vai ser difícil.

Você tá no começo, literalmente tanto faz. De preferência procure um conjunto de tecnologias que é usado nas empresas da sua região. Depois você aprende outras. Daqui 5 anos as linguagens que hoje são consideradas as mais tops, já não vão mais ser. Nem se preocupa e se não assistiu ainda veja meus episódios sobre “Sua Linguagem Não é Especial” e “A Dimensão do Tempo” onde eu tento explicar mais essa visão de longo prazo.

A sétima pergunta é outra que ouço bastante ainda: “Pra irmos pro framework, precisamos dominar a linguagem pura ou basta saber do básico ao intermediário? Por que as vagas de emprego hoje em dia tudo pede eles, perdi duas vagas em entrevista porque eu disse que sabia a linguagem pura.”

Você precisa saber obviamente, tanto a linguagem quanto o framework. Por exemplo, Javascript e React. Python e Django. PHP e Laravel. Ruby e Rails. Java e Spring. Você nunca vai conseguir saber 100% de qualquer linguagem. Isso leva anos, provavelmente nem os criadores dessas linguagens conseguem saber tudo que elas fazem hoje, porque é como um idioma. Nem Shakespeare saberia tudo de inglês. Só com a linguagem, sem ter conhecimento do ecossistema ao redor, você tem bem pouco valor pro mercado. O mercado adota frameworks, bibliotecas, ferramentas e processos que você vai precisar saber. Portanto não tem nenhum problema em começar pelo framework e aprender o mínimo da linguagem pra conseguir copiar e colar trechos que viu no stackoverflow. Muita gente faz isso.

Mas deixa eu colocar isso bem claro: é muito fácil pra qualquer sênior olhar pro código feito dessa forma e saber que é trabalho de um iniciante. Vira e mexe eu avalio código e de bater o olho eu já sei se é um iniciante ou alguém mais experiente. Sei quais trechos tem cheiro que foram copy e paste. De novo, pensa um idioma. Se você faz uma redação, algumas partes você mesmo escreve, outras partes você abre um livro de Shakespeare e copia trechos, abre um livro de Mark Twain e copia outro trecho. Você não acha que o estilo da codificação fica diferente? Alias, fica bem diferente? Por isso eu enfatizo tanto pra você treinar copiando código dos outros o máximo que puder. Com o tempo você vai começar a notar esses padrões. Comprimento de métodos, complexidade do código, estética da organização, e com o tempo vai começar a se preocupar não só com a função, mas com a forma do seu código. Vai identificar o que é sujeira desnecessária, vai aprender a reduzir seu código ao mínimo necessário que deixa ao mesmo tempo legível e também eficiente.

Não se preocupe, seu código no começo vai ser sempre ruim. Não importa se você começou aprendendo primeiro a linguagem e depois pulou pro framework, ou se começou pelo framework e foi aprendendo a linguagem no caminho. Qualquer das duas opções você tem que continuar estudando os dois. E se você parar, qualquer um mais experiente vai saber qual é seu nível só de olhar pro seu código. Aliás, isso é outra dica: se alguém que se diz senior, olhar pro seu código de junior, e não tiver nada a dizer, provavelmente é porque ele também é junior e só tem o título de senior.

A oitava pergunta é uma continuação. Depois de ter passado do estágio de aprender o básico de uma linguagem e dos frameworks, a dúvida é: “Eu tenho muita dificuldade com CSS, não consigo encaixar as coisas onde eu quero na página, e também tenho dificuldade com responsividade. A maior parte do meu trabalho é construindo APIs REST, e alguns back-offices com o próprio Rails. Meu foco é no backend, mas acabo mexendo um pouco com front e deploy no Heroku. Como profissional, eu devo focar meus estudos em superar essa dificuldade com CSS ou focar na minha carreira de backend e estudar TDD, design patterns, fundamentos, etc? Lembrando que meu foco é ser desenvolvedor fora do Brasil.”

A velha discussão sobre desenvolvedor full-stack. Isso existe e não existe. Em pequena escala é possível ser full-stack. Mas em grandes tech startups, que já tem alcance e escala, o trabalho de engenharia é muito mais especializado. Então a discussão se divide entre empresas onde é possível ser full-stack e onde não é. E a barreira é a quantidade de conhecimento avançado e experiência que o profissional precisa ter. Pra que direção você vai depende que tipo de empresa que você tem como objetivo. Tanto no Brasil quanto no Exterior. Empresas menores tendem a querer mais um “full-stack” que no fundo é alguém que consegue desenvolver e deployar uma aplicação web de ponta a ponta sozinho, mas sem se preocupar com escala e segurança de tech startup nível Uber. Claro que o que esse full-stack vai saber em termos de conhecimento e experiência é muito menos que um back-end especializado senior de um Uber.

Agora, o que foi listado como TDD, design patterns, fundamentos, ainda é só o básico. Tanto pra empresas médias, quanto pras grandes isso é be-a-bá, coisas que juniors já tem que estar estudando, plenos já tem que saber. Back-end senior tem ir além disso. Mas o que vai determinar até onde você vai é o quanto você tem disposição pra buscar coisas novas versus se conformar onde está e evitar mudanças.

A nona e penúltima pergunta é outra que não tem uma resposta satisfatória. A pergunta é: “Onde você vê mais vagas de emprego. No front-end ou back-end?”

Dependendo de que site de empregos ou post analíticos você procurar, vai achar respostas diferentes. Dependendo se está olhando só na sua região, nos grandes centros, em outros países. Depende. E de novo, como eu disse antes, existe escassez de profissionais de qualidade e existe excesso de profissionais inexperientes ou ruins mesmo. Se você for um programador ruim, não tem vaga decente em quase nenhum lugar. Se você se esforçou pra ser qualificado, existe vaga sobrando. E tanto faz se é em front, se é em back, em devops, em infosec. Pense sempre assim: se você sabe pouco, tem muito mais concorrência, portanto você vale menos. Se você se esforçou pra saber mais, tem muito menos gente que se esforçou, então a concorrência é menor. É lei da oferta e da procura. Use essa lei a seu favor e acumule mais conhecimento e experiência do que a maioria está fazendo. Se você está seguindo a maioria, você provavelmente está indo na direção errada.

E finalmente, a décima e última pergunta de hoje diz: “Meu sonho é ser um engenheiro de software, programo já tem 5 anos e amo fazer isso, mas ao mesmo tempo amo empreender. Estou criando minha empresa, vai ser um produto saas. Será que um dia vou ter que tomar a decisão de ou empreender ou programar? Os dois consigo fazer? Ouvi dizer que um dia se eu quiser que a empresa tenha sucesso vou ter que deixar de codar.”

Empreender, principalmente no mundo de tech startups, tem um fator que é pouco dito: o fator QI, que é Quem Indica. Nada é garantido no mundo de empreendimento, mas se você já é bem conectado, seja por histórico familiar e coisas assim, você sempre vai ter chances melhores de conseguir parcerias melhores, investidores e tudo mais. Se como eu, você não teve nada disso, sua única chance é conseguir isso do zero e confiança é algo que se adquire rápido. Certamente não tem vantagem nenhuma ir em evento de networking trocar cartão.

Criar um produto é razoavelmente fácil. Se você não for ruim demais, eventualmente consegue codificar um produto. Se ele vai ser bom ou não é outros quinhentos. Porém o maior custo de um produto do zero é o marketing. Estou falando numa ordem de grandeza maior do que você gastar pra fazer o produto. Um produto ruinzinho que tem um marketing caro tem mais chances de atrair usuários do que um produto extremamente bem feito que ninguém nunca ouviu falar. Eu falei que estamos no meio de um boom de tech startups, significa que a concorrência é enorme. Ser só um pouquinho melhor do que a concorrência, ainda mais se for só subjetivo, não basta. Pra conseguir crescimento orgânico só na competência técnica, sem apoio externo, é basicamente sorte.

Se seu objetivo é ser um engenheiro de software bom, isso vai de encontro a empreender, que também é uma atividade de tempo integral. De novo, se você já não tem excelentes sócios administradores, que conseguem perfeitamente levar a parte tributária, fiscal, vendas, marketing, suporte, recursos humanos, você vai ter que fazer tudo isso sozinho. É muito raro, sozinho, conseguir fazer tudo e ainda conseguir ter alguma chance de ter sucesso. Já vi gente conseguir, mas é a exceção da regra. Por isso não é uma recomendação.

E isso é outra coisa que eu falo pra todo iniciante na carreira. Empreender muito cedo não é nunca uma recomendação. Administrar uma empresa de maneira inteligente e eficiente, especialmente no começo, é tão difícil quanto fazer um bom código que funciona, sem bugs e que escala. Não superestime suas capacidades de programação e nem subestime a dificuldade de ser empresário com as leis toscas do Brasil. Aqui é uma preferência pessoal, mas eu prefiro ir um caminho de cada vez. Primeiro ganhar calo como programador de verdade em uma variedade de projetos diferentes até se tornar realmente bom e com o tempo ver como as empresas realmente funcionam e durante esses projetos ir conhecendo pessoas fora da sua área como advogados, contadores, administradores, que você passe a confiar e que também confiam em você depois de ver resultados, e naturalmente você pode se tornar um empresário.

Tudo isso muda se você já tem dinheiro, vem de família abastada, e pode contar com esse networking e recursos, claro. Mas não é a realidade da maioria das pessoas. O caminho pra se tornar um bom profissional, um bom engenheiro de software, não é simples. E não deveria ser mesmo, por isso ele é bem remunerado. Justamente porque a maioria das pessoas desiste no caminho, é desonesto, não consegue admitir as próprias fraquezas e por isso nunca sabe onde precisa melhorar, e gasta mais tempo botando defeito no mundo e nas outras pessoas do que em si mesmo. Eu sempre falo que se alguém reclama tempo demais, é justamente o tempo que não está usando pra praticar, estudar e entregar valor de verdade. Acho que falei no video sobre SAP que no mundo de consultoria a gente tem um nome pra isso: consultor crocodilo, que é aquele que tem boca grande e braço curto.

E por hoje é isso aí, não dá tempo pra responder todas as outras perguntas que recebi, mas essas representam a maioria das coisas que me perguntam. Eu tentei cobrir os cenários que eu acho que a maioria das pessoas se encaixam, seu caso particular talvez não esteja aqui. Também não tem problema me mandar perguntas em privado pelas redes sociais, mas eu realmente não consigo responder todo mundo de bate pronto, mas pode ser muito útil pra um próximo vídeo. Vocês gostam desse tipo de video de vez em quando? Não deixem de dar feedback nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e não deixem de clicar no sininho pra não perder os próximos episódios. Aliás, o YouTube vira e mexe tira a sua assinatura do canal se você não volta com alguma frequência, então chequem se ainda estão assinando! Por hoje é isso aí, a gente se vê, até mais!

Olá pessoal, Fabio Akita

Antes de mais nada, meus agradecimentos ao Codigo Fonte TV pelo convite pra compartilhar nossas mensagens pra comunidade brasileira de desenvolvimento de software.

Estamos nos anos 20 do século XXI eu fico pensando uma coisa. Faz só 6 décadas que começou a revolução do circuito integrado. Faz só 5 décadas que começou a revolução dos computadores desktop. A revolução da Web ainda não completou 3 décadas. E faz só 1 década que tivemos a revolução dos smartphones. Durante minha carreira de 30 anos de programação eu tive o privilégio de testemunhar boa parte dessa história em primeira mão e mesmo assim eu não consigo imaginar o que vai acontecer na próxima década. Não existiu melhor momento pra se tornar um programador do que agora e fazer parte dessa história que vai continuar transformando a humanidade. E não tem nada mais empolgante pra mim do que saber como essas coisas funcionam e ter parte da construção do futuro. Juntem-se a nós!

Muita gente me procura via mensagens diretas em redes sociais, em particular pelo Instagram. Pro episódio de hoje eu selecionei 10 perguntas que representam a maior parte dos temas que costumam me perguntar e apesar de já ter parcialmente respondido algumas delas via os temas de outros vídeos passados, vou tentar compilar respostas um pouco melhores hoje. Espero que ajude.

O tema vai ser “Perguntas mais frequentes de iniciantes no mercado de tecnologia”. Diferente do vídeo que eu fiz chamado “Começando na Carreira de TI” (https://www.youtube.com/watch?v=H84WD_xyj10) onde eu abordei assuntos que começam mais na escolha da faculdade primeiro, esta é mais focado no primeiro emprego e na forma de começar a evoluir. Depois de assistir este video convido vocês a tentarem o vídeo “Dimensão do Tempo” (https://www.youtube.com/watch?v=Qb5b8ZE9tIY) e “O Mercado de TI para Iniciantes em Programação” (https://www.youtube.com/watch?v=O76ZfAIEukE) também.

Se vocês ainda não me seguem nas redes sociais, não deixem de seguir. E podem mandar perguntas em privado que eu possa usar em próximos episódios de perguntas e respostas.


[Akitando] #68 - Entendendo Conceitos Básicos de CRIPTOGRAFIA | Parte 2/2

DESCRIPTION

Errata: em 12:08 eu escrevi "Script" mas é "Scrypt" (com "y"). E eu falei errado. Giga já é bilhão e Tera são trilhões! um Antminer S17 faz mais de 70 TRILHÕES de hashes por segundo!

Se você não assistiu a Parte 1, eu dei uma introdução à encriptação simétrica, algoritmos de hash como SHA256, HMAC. Se você já sabe essas coisas pode pular direto pra esta Parte 2.

Desta vez vou continuar no problema da tabela de usuários roubada por hackers e se você protegeu adequadamente. Passando por uma analogia de como Bitcoins são minerados e como os mineradores tem acelerado esse processo e o que isso tem a ver com algoritmos de derivação de chaves como PBKDF2, Bcrypt e outros, que diferença fazem comparados a algoritmos de hashing comuns como HMAC.

E finalmente vamos ver sobre Diffie-Hellman, RSA, os prós e contras e o que Curvas Elípticas adicionam a essa solução e como tudo isso forma o TLS e SSH modernos.

Seções:

  • 01:10 - recapitulando, tabela roubada
  • 02:18 - mineração de Bitcoin
  • 06:03 - PBKDF2
  • 11:44 - Bcrypt, Scrypt, Argon2
  • 16:52 - Base64, CRC, DV
  • 19:37 - full disk encryption + key derivation function
  • 22:55 - Diffie-Hellman
  • 29:52 - RSA
  • 36:16 - Entendendo TLS
  • 41:04 - Curva Elíptica

Links:

  • Serious Security: How to store your users’ passwords safely (https://nakedsecurity.sophos.com/2013/11/20/serious-security-how-to-store-your-users-passwords-safely/)
  • Password Hashing: Scrypt, Bcrypt and ARGON2 (https://medium.com/@mpreziuso/password-hashing-pbkdf2-scrypt-bcrypt-and-argon2-e25aaf41598e)
  • Why Bitcoin mining ASICs won't crack your password (https://rya.nc/asic-cracking.html)
  • RSA Encryption (https://brilliant.org/wiki/rsa-encryption/)
  • How RSA Works With Examples (http://doctrina.org/How-RSA-Works-With-Examples.html)
  • Understanding the SSH Encryption and Connection Process (https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process)
  • SSH-KEYGEN - GENERATE A NEW SSH KEY (https://www.ssh.com/ssh/keygen/)
  • Upgrade Your SSH Key to Ed25519 (https://medium.com/risan/upgrade-your-ssh-key-to-ed25519-c6e8d60d3c54)
  • A (relatively easy to understand) primer on elliptic curve cryptography (https://arstechnica.com/information-technology/2013/10/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/)
  • Supersingular isogeny key exchange (https://en.m.wikipedia.org/wiki/Supersingular_isogeny_key_exchange)
  • What is TLS and how does it work? (https://www.comparitech.com/blog/information-security/tls-encryption/#What_does_TLS_do)

SCRIPT

Olá pessoal, Fabio Akita

Pois bem, vamos pra parte 2 do tema de conceitos básicos de criptografia. Semana passada eu vomitei um tanto de informação, e espero que vocês tenham conseguido acompanhar. Esta semana não vai ser diferente. Portanto peguem papel e caneta de novo e preparem-se pra fazer muitas anotações.

Da mesma forma como na semana passada, a matemática é muito pesada, e eu não sou a pessoa mais indicada pra descer nos detalhes das provas matemáticas. Já vou me considerar com sorte se eu não confundi os conceitos durante as explicações. Se tiverem interesse, na internet existem centenas de documentações explicando toda a matemática em detalhes. Vale a pena dar uma olhada, porque se eu fosse tentar explicar em video, teria que fazer um outro canal inteiro do tamanho do Numberphile.

Recapitulando, semana passada falamos sobre encriptação de chave simétrica, falamos sobre algoritmos de hashing e message digest, falamos sobre derivação de chaves. Agora vamos continuar o assunto começando a mostrar as ramificações por cima disso.

Continuando a nossa historinha hipotética da tabela de senhas roubadas. Já estabelecemos que não devemos gravar senhas dos usuários em plaintext numa tabela em nosso banco. Também já estabelecemos que não devem ser encriptadas, e agora tentamos gravar o hash. Mas como já vimos, é fácil atacar com dicionários ou rainbow tables com hashes pré-calculados. E como ainda existe o paradoxo do aniversário que garante que existe uma boa probabilidade de colisões, é possível encontrar uma palavra que gere o mesmo hash da senha do usuário, mesmo que não seja exatamente a mesma senha. Pra evitar esses ataques, uma forma de mitigar é concatenar algum número grande junto com a senha e daí gerar o hash. Então pra cada novo usuário cadastrado, geramos esse número que chamamos de salt, daí fazemos o hash da senha prefixado com esse salt e gravamos o hash resultante no banco, junto com o salt. Daí vai ser mais difícil um ataque com rainbow tables pré-calculadas. Se você já fuçou o código fonte de qualquer sistema de autenticação, vai encontrar esse campo salt ou equivalente.

Então, resta ao hacker tentar calcular os hashes na hora, usando os salts, via força bruta, concatenando palavras de um dicionário com o salt que está na tabela, até achar um hash que tenha colisão com o hash gravado na tabela. Parece difícil, porém a geração de hashes pode ser acelerado. Aqui é mais uma especulação, nem sei se fazem isso. Mas faça de conta. Se você acompanhou a corrida pelo Bitcoin nos últimos anos deve ter ouvido falar que os tais mineradores acabaram com o estoque de GPUs do mercado em 2017. E que mineradores gigantes como a Bitmain criaram ASICs ou Application Specific Integration Circuits. Lembram o que eu falei dos FPGAs? FPGAs são circuitos programáveis via software, é como se eu pudesse “moldar” o hardware do chip como se fosse um chip feito sob medida. ASICs são exatamente chips feitos sob medida direto, direto da fábrica. Existem ASICs feitos exclusivamente pra gerar hashes específicos de mineração, os famosos Antminer S9 que é uma combinação de ASIC, FPGAs e processadores ARM como controladores. Pra dar uma idéia, um CPU topo de linha como o Intel Core i9 9900K tem o que chamamos de hash power, que é a potência de geração de hashes. São uns 478 hashes por segundo.

Parece bastante? Nah, isso é uma porcaria. Uma GPU pode ser usada pra gerar hashes mais rápido ainda. Uma NVIDIA TITAN X, arquitetura Pascal, que é a geração passada, consegue fazer 1.3 gigahashes por segundo, ou seja, 1.3 milhões de hashes por segundo. É ordens de grandeza mais rápido que uma CPU. E eu posso ligar mais de uma GPU no mesmo computador em paralelo. Mas isso também é uma porcaria, uma única unidade do Antminer S9 consegue fazer uns 14 terahashes por segundo, ou seja, 14 bilhões de hashes por segundo. Agora estamos começando a conversar.

Sem entrar muito nos detalhes de como Bitcoin funciona, o que chamamos de proof of work, que é o processo de assinatura de um bloco de Bitcoin é basicamente achar um hash especial desse bloco. Não é um hash qualquer, você precisa ir alterando um número dentro desse bloco que altera o hash final, até achar um hash que começa com um certo número de zeros à esquerda. Mas você lembra que eu falei que algoritmos de hash precisam embutir os princípios de confusão e difusão de Shannon, ou seja, se eu mudar 1 bit na mensagem original, mais da metade dos bits do hash precisam mudar, de forma que não seja fácil achar um relacionamento entre a mensagem original e o hash final. Se eu ficar alterando só um número dentro do tal bloco de transações de Bitcoin e refazer o hash, ele vai ser completamente diferente do hash anterior, de forma que o comportamento é parecido com a geração de um número pseudo aleatório. Então eu preciso ficar alterando esse um número do bloco e regerando hashes até conseguir achar um hash específico que tem a tal sequência de zeros à esquerda. Essa quantidade de zeros é o que chamamos de dificuldade da mineração.

E você imagina que um minerador precisa de muito hashpower, porque minerar é uma competição. Centenas de mineradores estão tentando assinar o mesmo bloco ao mesmo tempo. Quem achar o hash primeiro ganha um prêmio em Bitcoin, uns 12 bitcoins agora em novembro de 2019. Além do prêmio o minerador ganha as taxas de transação acumuladas nesse bloco. Quem perder, não ganha nada. Por isso quanto mais hashpower o minerador tiver, mais chances tem de achar o hash primeiro. E por isso se usa ASICs como o AntMiner e por isso só com GPUs ou CPUs você não tem chances de competir, porque estamos falando de pular de milhões de hashes por segundo com GPUs pra bilhões de hashes por segundo com Antminers.

Mas, não só de mineração de Bitcoins vive geradores de hashes. Imagine você colocar uma versão derivada de Antminer feita exclusivamente gerar hashes normais. Pra tentar achar a colisão via força bruta numa tabela de senhas roubada. Mesmo com salt, um equivalente Antminer pode ficar permutando num dicionário, concatenando cada palavra com o salt e gerar o hash na hora pra comparar. É um processo parecido com mineração de Bitcoins, mas pode ser usado pra tentar quebrar senhas via força bruta, à uma velocidade de bilhões de hashes por segundo por Antminer. E como cada Antminer custa só 3 mil dólares, imagine se eu colocar um data center de derivados de Antminer com o único objetivo de estourar senhas. Especialmente se forem senhas importantes como senhas de banco ou senhas de funcionários do governo americano.

Voltando à nossa história da tabela de usuários roubada, por isso mesmo, só fazer o hash de senhas, mesmo com o recurso de concatenar com salts antes de hashear, ainda oferece o risco iminente de encontrarmos colisões usando ASICs especializados. E agora o problema é outro, quanto mais rápido for a performance de um algoritmo de hashes, mais rápido esse ASIC hipotético conseguiria processar. Então existe uma outra forma de proteger. E é usando algoritmos de derivação de chaves com capacidade de adicionar dificuldade no processamento. Um dos mais conhecidos hoje é o Password-based Key Derivation Function ou PBKDF2. Como o nome diz ele é uma função que deriva chaves a partir de senhas.

Vamos entender, uma senha normal é normalmente muito fraca. Não importa se você acha que fez ela ser forte trocando “A” por arroba ou “I” por exclamação, essas coisas qualquer atacante já adiciona no algoritmo de força bruta pra quebrar sua senha. Parta sempre do princípio que sua senha é uma bosta. Por isso sistemas de autenticação baseadas em Kerberos, por exemplo, não usam sua senha diretamente, primeiro elas modificam sua senha num processo conhecido como key strengthening ou key stretching, literalmente fortalecimento e esticamento de chaves. Elas não são necessariamente a mesma coisa, mas pra esse vídeo vamos assumir que são. É basicamente o processo de adicionar um salt gigante, algo com mais de 64-bits de comprimento, e aplicar alguma função HMAC de hashes milhares ou centenas de milhares de vezes, um em cima do outro, e adicionar alguma entropia extra como o horário atual, algumas vezes no meio do processo.

Isso tem duas funções, a primeira é gerar uma nova senha mais forte e mais longa que você pode usar no lugar da sua senha curta e fraca. É uma senha derivada da sua, mas que vai resistir a rainbow tables e dificultar colisões. Mais do que isso, algoritmos como o PBKDF2 tem um fator de dificuldade, parecido em conceito com o processo de proof of work que é usado na mineração de Bitcoins. É literalmente quantas vezes você vai rodar o algoritmo de hash como um SHA2 ou HMAC-SHA1. Se você rodar dezenas de milhares de vezes, o algoritmo vai custar meio segundo, talvez até um segundo pra gerar o hash final.

Com o que falei até agora, você já deve estar entendendo porque é desejável um algoritmo custar caro pra gerar o hash. Eu mencionei a possibilidade de usar um similar a Antminer, um ASIC, pra quebrar senhas hasheadas com salts. Eu fico falando similar, porque na prática você não vai usar um Antminer de verdade, porque a lógica do circuito está feita especificamente pra lógica de proof of work de mineração de Bitcoin. Porém, com um FPGA ou desenhando um novo ASIC específico pra SHA256 por exemplo, ou no mínimo usando GPUs, você já está na casa dos milhões de hashes por segundo. Com um novo ASIC você poderia ir pra bilhões de hashes por segundo. Sabendo que o custo de desenvolvimento de hardware decai todo ano, em breve ficará acessível criar equipamentos assim muito barato, na China. Por isso começamos a migrar pra algoritmos de geração de hashes como o PBKDF2 que custa caro pra processar.

Mas essa não é a única solução, é uma corrida entre aumentar a dificuldade do algoritmo versus conseguirem criar hardware mais rápido com mais hashpower. Imagine o seguinte problema. Digamos que no cadastro de usuários do seu sistema, você resolveu se precaver e escolheu usar PBKDF2 com HMAC-SHA1 com um salt de 64-bits e com dificuldade de umas mil rodadas. Isso leva alguns milissegundos pra gerar o hash e é isso que você vai gravar no banco. Com hardware atual, isso levaria milhares de anos pra achar uma colisão. Dê mais 2 ou 5 anos, digamos que consigam fazer esse tal ASIC, barato, que comece a achar colisões. E se agora eu quiser aumentar a dificuldade e migrar todos os hashes pra usar SHA2, com salts de 128-bits e aumentar pra 100 mil rodadas? Eu não tenho como fazer isso, porque obviamente eu também não tenho a senha original do usuário.

Por isso, outra forma de mitigar que algumas aplicações implementam é atualizar o hash do banco quando o usuário se logar com sucesso da próxima vez, porque é só nesse momento que você tem a senha do usuário de novo. Daí pode gerar um novo hash com maior dificuldade e atualizar no banco. O hash de um PBKDF2 é o hash concatenado do salt, do número de iterações da dificuldade, por isso ele é bem comprido e por isso você não precisa criar outro campo na tabela só pra guardar o valor do salt.

Além de aumentar a dificuldade exigindo mais processamento, existem outras formas de mitigar. Por exemplo, o custo de memória RAM costuma ser proporcionalmente maior do que CPUs, ao longo do tempo. Elas diminuem menos rápido de preço. E se criarmos um algoritmo de hash que além de ser caro pra processar também exige um uso de memória bem elevado. E é isso que faz algoritmos como bcrypt, scrypt e outros novos como Argos2. Finalmente chegamos no bcrypt que muitos tutoriais hoje recomendam usar, seja em PHP ou Ruby, você vai achar bibliotecas e tutoriais que usam o bcrypt pra fazer o hash da senha. Assim como o PBKDF2 ele tem dificuldade configurável, então pra produção você deixa uma dificuldade elevada, mas pra sua máquina de desenvolvimento ou pra ambiente de testes com ferramentas de continuous integration, você deixa uma dificuldade baixa pra rodar rápido.

O bcrypt na realidade não é um algoritmo específico de hash, ele pode ser usado como uma função de derivação de chaves através de algoritmo de encriptação simétrica, no caso o cipher Blowfish. O Blowfish foi criado como uma alternativa mais segura e mais rápida que o DES. Lembram que falei dele no episódio passado? Na verdade, acho que qualquer coisa hoje em dia é mais seguro e mais rápido que o DES. O Blowfish tem uma característica importante que nos interessa: ele um custo de setup alto, ele precisa de 4kbytes de memória pra cada novo setup. Parece pouco, mas isso escala rápido e torna o Blowfish caro em termos de uso de memória. Vocês lembram que uma das formas de deixar o DES mais rápido era rodar o algoritmo três vezes por bloco. Pois bem, existe uma derivação do Blowfish chamado TwoFish que foi um dos finalistas na competição da NIST pra escolher quem seria o novo AES. Mas como falei no episódio anterior, quem ganhou foi o Rijndael.

De qualquer maneira, podemos usar o Blowfish pra gerar hashes. Isso é uma coisa que nem todo mundo sabe: hashes podem ser gerados a partir de algoritmos de encriptação de chave simétrica. O bcrypt é um exemplo disso, usando o Blowfish pra gerar hashes. Diferente do PBKDF2 você não precisa gerar e passar um salt porque o bcrypt gera um salt automaticamente, por isso se você rodar o bcrypt várias vezes em cima de uma mensagem, ele vai gerar hashes diferentes toda vez. E assim com o PBKDF2 você pode configurar a dificuldade, o custo dele. Inclusive o parâmetro se chama “cost”. Assim, quanto maior o custo, mais rodadas ele vai fazer e mais demorado vai ficar. Mas diferente do PBKDF2, só com um ASIC você não vai conseguir acelerar um ataque de força bruta porque além de hashpower você vai precisar de muita RAM, elevando o custo, e dificultando alguém tentar algo assim.

Existem outros sucessores ao bcrypt como o scrypt e Argon2, que foram desenhados especificamente pra serem mais caros ainda em uso de memória e dificultar ataques de hashpower via GPUs ou ASICs. Muitos frameworks web hoje usam o bcrypt porque ele é melhor que nada. Mas eu imagino que se alguém precisasse escolher o algoritmo de hash mais resistente e moderno seria ou o Argon2 que é o mais flexivel em termos de customização ou no mínimo o scrypt.

Concluindo a parte de como proteger senhas, sabemos que jamais, nunca, devemos gravar em plaintext. Também sabemos que não adianta encriptar a senha e gravar no banco. Aprendemos também que só usar um hash, seja um SHA256 ou um HMAC-SHA256 por si só, não vai resistir muito tempo a ataques de dicionários ou rainbow tables. Adicionar salts ajuda bastante, mas não vai ser resistente a ataques via hardware especializado como GPUs ou ASICs. Portanto, a melhor medida é usar um algoritmo de derivação de chaves que seja muito caro tanto em processamento quanto uso de memória como Argon2 ou scrypt, e de tempos em tempos, toda vez que um usuário se logar com sucesso, gerar um novo hash com uma dificuldade ainda maior e gravar no banco. Dessa forma mitigamos o máximo possível caso o banco de dados seja comprometido e os dados caiam na mão de hackers dispostos a quebrar suas senhas.

“Mas, meu site usa autenticação de dois fatores, só com a senha ele não vai conseguir entrar.” O grande lance de quebrar senhas não é pra usar só no seu próprio site. Um dos problemas com usuários normais é que eles usam a mesma senha em sites diferentes . Se num banco de dados de 1 milhão de pessoas, eu conseguir 100 mil senhas, são 100 mil possibilidades pra se usar em diversos outros sites que os usuários tenham contas. Por isso mesmo falamos que além de habilitar autenticação de dois fatores, cada conta em cada site que você tenha cadastro deve usar uma senha segura diferente. E por isso você devia estar usando ferramentas de gerenciamento de senhas como 1Password ou LastPass ou equivalentes. Eu faço isso, nenhuma conta minha tem senhas repetidas. Twitter, Facebook, GitHub, Instagram, Google, cada uma usa senhas de uns 40 caracteres aleatórios gerados por ferramenta, impossíveis de decorar.

Uma pequena tangente, eu já falei como muita gente confunde hashes de message digests, funções de derivação de chaves, ou mesmo UUID, GUID, IPv6, porque todos eles são strings com caracteres representando hexadecimais que parecem aleatórios. Existe ainda outro gerador que cospe strings que parecem bagunçadas que podem confundir, esse é o Base64. Base64 é uma ferramenta usada pra converter um binário em strings de texto. Cada caractere do base64 representa 6-bits, então três bytes de 8-bits são representados em 4 caracteres de 6-bits. Portanto sempre se lembre que converter um binário em texto via Base64 aumenta o tamanho final em pelo menos 20% se não forem comprimidos com gzip ou algo assim.

E porque eu preciso converter binários em texto? Simples, porque todo protocolo de internet como os de e-mail e mesmo HTTP são protocolos texto. Se eu quiser enviar um e-mail com um anexo que é um arquivo PDF ou word ou uma imagem, ele precisa converter em texto pra poder concatenar no corpo do e-mail. Sim, se você não sabia disso, todo e-mail é sempre 100% texto e todo binário precisa ser convertido em base64 pra ser anexado e por isso todo anexo de e-mail é mais pesado que o binário original, e por isso você deve evitar mandar anexos gigantes.

Eu acho que não preciso dizer isso a esta altura, mas eu fico pasmo toda vez que vejo um programador que acha que Base64 é uma forma de encriptação. Ela não é. Ela é só uma conversão simples de binário pra texto. Sem chave, sem entropia, sem nenhuma operação que bagunça seu conteúdo. Nunca use Base64 achando que está adicionando alguma dificuldade de obfuscação, pois não está. Base64 é trivialmente reversível do texto pro binário. Outro nome que talvez você veja por aí é ASCII armor ou Radix-64, mas é tudo Base64. A mesma coisa vale pra checksums ou CRC que é cyclic redundancy check, em resumo é um cálculo feito em cima de um número pra ver se algum dos dígitos está errado. Por exemplo, um código de barras de um boleto bancário tem um CRC, dessa forma é muito fácil checar se a pessoa digitou um número errado do boleto. Naquele códigozão que você digita no seu internet banking tem um CRC no final, que é um número calculado a partir dos anteriores. Digite um número errado e o CRC não vai bater. CRC também é útil pra checagem de erros no tráfego de pacotes de rede. O protocolo TCP tem uma checagem de CRC meio fraco, e por isso existe outra checagem na camada 2, no nível do frame de Ethernet por exemplo. É uma forma de rapidamente checar errors de transmissão causado por barulho ou interferência na rede por exemplo. Mas isso também não tem nada a ver com hashing ou encriptação.

Até agora tudo que falamos de encriptação depende de um componente: um segredo, ou uma chave. Pra coisas como encriptar seu HD, isso é suficiente. Quando você usa o BitLocker do Windows 10 Pro ou o FileVault do MacOS ou o LUKS no Linux, eles vão fazer um processo similar. Você começa providenciando uma senha que deveria ser forte. Mas como ao mesmo tempo você não quer esquecer a senha e perder acesso aos seus dados, provavelmente você vai acabar usando uma senha porcaria. Mas não tem problema, o sistema de encriptação vai gerar uma senha forte pra você, usando um salt gigante com um algoritmo de derivação de chave como o Scrypt ou PBKDF2 que, como já expliquei, são resistentes a ataques de força bruta ou dicionários e até ASICS. Aqui eu estou chutando, mas essa ainda não deve ser o segredo pra encriptação. Ele vai gerar outra senha forte, que seria o mount key. Esse mount key é que vai ser usado pra encriptar os dados do seu disco. E a senha forte derivada da sua vai ser usada pra encriptar o mount key.

Dessa forma, se amanhã você quiser mudar sua senha ou passphrase, você vai só mudar a derivação que abre seu mount key. E não precisaria decriptar e encriptar seu disco inteiro tudo de novo. Adicione ainda um token ou identificação biométrica como autenticação de dois fatores e você tem um pouco mais de segurança. Entenda que além dos seus dados, tem outra coisa importante que precisa ficar encriptado: o seu swap. O swap é como se fosse uma extensão da RAM em disco. Não dá pra saber o que tem nesse swap. Se alguém pegar seu swap lá pode ter o que tinha na memória das aplicações que estavam rodando na máquina. Números de cartão de crédito, mesmo senhas, ou outros dados sensitivos podem estar lá. Você gostaria que a memória também esteja encriptada.

A Intel, por exemplo, tem o SGX ou Safe Guard Extensions, em resumo ele garante que dados específicos que saem do processador pra memória já saiam direto do processador encriptados, colocados numa região específica da memória que chamamos de “enclaves” ou secure enclaves. Pense numa partição criptografada dentro da RAM que só é lida e escrita pela CPU e nem seu sistema operacional tem acesso, nem como root, nem como administrador nem nada. Não é 100% do que está na RAM mas só coisas específicas que você manda via uma API. É uma API que pode ser usada por softwares que precisem trabalhar com dados sensíveis como sua chave mestra, de forma que ela não fique nem por um milissegundo exposta na RAM em plaintext e suscetível a ser roubada dali por um malware.

Resumindo até agora. Falamos de algoritmos de hashing, tanto obsoletos como MD5 e SHA1 quanto mais novos como HMAC-SHA256. Falamos de derivação de chaves como Bcrypt e PBKDF2. De encriptação simétrica de chave. Coisas como o AES Rijndael ou Blowfish ou TripleDES que são todos algoritmos block cipher, por processarem um bloco de cada vez. Mas existem outros algoritmos como RC4 que são stream ciphers, que aplica processamento bit a bit em vez de bloco a bloco. Mas pros propósitos de hoje, só block ciphers é suficiente. Falta falarmos de encriptação assimétrica e troca de chaves.

Tudo que falamos até agora de encriptação depende de uma chave secreta, é a chave pra encriptar e decriptar. Dentro da mesma máquina tudo funciona. Mas e quando queremos mandar um ciphertext pra outra pessoa? Pra essa outra pessoa decriptar a mensagem ele precisa ter o mesmo segredo usado pra encriptar. Só que não existe nada mais inseguro do que trafegar uma senha pela internet. Essa senha vai ser certamente interceptada no meio do caminho e toda sua segurança vai por água abaixo. Queremos um jeito de criar uma chave na minha máquina e na máquina de outra pessoa, sem trafegar as chaves, de tal forma que o que eu encriptar com a chave que só eu tenha seja possível de ser decriptada com a chave que só a outra pessoa tenha. Esse é o conceito.

Pra simplificar vamos falar das duas mais importantes. Diffie-Hellman e RSA. Ambas cujos nomes são os nomes dos criadores, no caso Whitfield Diffie e Martin Hellman e no caso de RSA é Rivest, Shamir e Adleman. Vocês devem ter percebido que muitos algoritmos de segurança ganham uma derivação do nome dos criadores, mesma coisa com Rijndael que é o AES, por exemplo. E tanto Diffie-Hellman e RSA por coincidência tem minha idade, já que nasceram em 1977, o ano onde descobrimos que é possível trafegar comunicação encriptada sem o risco de trafegar uma chave secreta e eu argumentaria que é a partir desse ponto que a internet realmente se torna comercialmente viável e anos depois permitiria coisas como e-commerce.

Já avisando que felizmente é fácil explicar como esses algoritmos funcionam, mas vai ser impossível demonstrar “porque” eles realmente funcionam, falta matemática pra explicar aritmética modular, teorema de Euler, totientes e muito mais, então de novo, por hoje vocês vão ter que acreditar no que eu disser, mas eu recomendo que você pesquisem as provas matemáticas. Foi isso que diferenciou Diffie-Hellman, porque eles conseguiram demonstrar matematicamente a segurança do algoritmo.

Pra que serve Diffie-Hellman? Ele possibilita que duas pessoas consigam gerar duas chaves, separadamente, cada um gerando sua própria senha em sua própria máquina, sem nunca trafegar essas chaves. E eles garantem que, o que a chave de um encriptar, a chave do outro consegue decriptar. Conseguem entender as implicações? Todo mundo explica Diffie-Hellman usando cores ou números pequenos pra facilitar a explicação, então vou roubar a explicação da Wikipedia porque é bem simples.

Imagine duas pessoas na internet, Alice e Bob. Primeiro eles trocam duas informações públicas, de modo inseguro via internet mesmo, um número é o módulo p igual a 23, por exemplo, e uma base g igual a 5, e 5 porque ela é uma raíz primitiva módulo 23. Falei que precisa de aritmética modular. Daí Alice escolhe um segredo chamado azinho que ela não divulga pra ninguém, um número como 4. Agora vai complicar um pouco. Alice precisa calcular o g, que é um número público, elevado a esse azinho, que é secreto, módulo p, ou seja, 5 elevado a 4 módulo 23 que por acaso é 4 de novo, porque estamos usando números muito pequenos. Esse resultado 4, que vamos chamar de Azão, ela pode mandar publicamente pro Bob via internet insegura.

Agora o Bob também escolhe um segredo que ele não divulga, um bezinho igual a 3, e faz a mesma conta de g elevado a bezinho módulo p, ou seja, 5 elevado a 3 módulo 23 que é 10. Vamos chamar isso de Bezão e Bob pode mandar esse Bezão pra Alice via internet também, aberto. Alice agora pode computar o Bezão do Bob elevado ao azinho dela módulo p. Ou seja, 10 elevado a 4 módulo 23. Isso vai dar 18. E o Bob pode fazer o mesmo, pegar o Azão que recebeu da Alice, elevar com o segredo bezinho dele que é 3, e fazer módulo 23 e o resultado é 18 também. Pronto, tanto Alice quanto Bob compartilham o mesmo segredo 18 e esse número nunca trafegou pela internet, e mesmo sabendo o módulo 23, a base 5, o Azão 4 da Alice e o Bzão 10 do Bob é impossível chegar no segredo 18 que só os dois conseguem ter.

Uma pessoa que ouviu a comunicação no meio do caminho não tem como calcular o segredo. Essa é a propriedade de grupo multiplicativo de inteiros módulo p. Agora podemos fazer um derivação de chave desse segredo - ou no caso de um TLS/SSL eles fazem acho que só um hash simples via SHA2, porque acho que TLS antecede a criação do conceito de derivação de chaves. E esse hash pode ser usado como o segredo por ambos num algoritmo de encriptação de chave simétrica, como o AES, e trafegar mensagens encriptadas inviáveis de decriptar, como já expliquei antes sobre o AES. Relembrando que a idéia de fazer uma derivação de chaves via hashes seria pra esticar e fortalecer a chave.

Claro, esse exemplo usa inteiros miseravelmente pequenos, na realidade o tal módulo p vai ser um número primo gigantesco, digamos de 4096-bits. A dificuldade pra alguém de fora é descobrir qual número que podemos elevar o Azão ou o Bezão módulo p que vai nos dar o segredo. E pra achar esse número caímos no problema de logarítmo discreto, que é computacionalmente inviável de resolver, e esse é o problema que só um algoritmo quântico de Shor conseguiria tentar resolver, num computador quântico de mais de 4000 qubits, como eu já expliquei no episódio anterior.

Diffie-Hellman é bom pra criar chaves temporárias. Mas tem um problema, ele sozinho não tem como autenticar as identidades da Alice e do Bob. Por exemplo, uma terceira pessoa chamada Eve podia fazer um ataque man-in-the-middle, ou seja, toda comunicação da Alice passa pela Eve e ela devolve pro Bob e vice versa. Daí a Alice e a Eve acabam combinando o segredo via Diffie-Hellman e depois a Eve e o Bob combinam outro segredo. E pronto, ela participando desse processo vai produzir segredos conhecidos com os dois e consegue ao mesmo tempo ver a comunicação e repassar a comunicação entre os dois, de tal forma que Alice e Bob não percebam que a comunicação está sendo interceptada e repassada.

Pra evitar esse ataque de man-in-the-middle existe outro protocolo usado por cima do Diffie-Hellman que é o STS ou protocolo Station-to-Station que em resumo exige uma checagem de assinaturas entre os participantes, pra validar as identidades, antes de entrar no processo de geração de chaves secretas. E isso envolve o uso de certificações de chave pública, onde tanto Bob quanto Alice tem um par de chaves privadas e chaves públicas. Isso chamamos de chaves assimétricas.

Pra entender chaves assimétricas, vamos de uma vez entrar no RSA. Aqui a matemática fica mais difícil. Se você já gerou chaves públicas pra cadastrar no GitHub provavelmente teve que usar o comando ssh-keygen e normalmente o padrão é usar a opção RSA. Em resumo hiper resumido, o processo começa escolhendo dois números primos gigantescos, de novo lá pela casa dos 2048 bits. Na verdade os tutoriais todos devem estar recomendando você escolher 4096 bits. O primeiro processo envolve a multiplicação desses dois primos, esse é o módulo da operação. Daí você escolhe um outro número coprimo da função totiente de Carmichael pra esse módulo, esse vai ser o expoente da operação.

A chave pública é a combinação do módulo e do expoente. Agora você acha o inverso multiplicativo modular do expoente inverso módulo esse totiente. Essa é a chave privada. Nem se preocupe em saber o que é função totiente ou inverso multiplicativo modular. Se quiser entender toda a matemática vai precisar saber o que é o algoritmo de extensão euclidiano, teorema de Lagrange, e de novo o tal grupo multiplicativo de inteiros módulo p que usamos em Diffie-Hellman. Mas entenda só que a operação inicia com dois números primos gigantes, com ele você calcula a chave pública primeiro e dela deriva a chave privada, e por isso elas são relacionadas.

A propriedade mais importante é que tudo que você encriptar com a chave pública só pode ser decriptada com a chave privada, e vice-versa. A chave privada você guarda num diretório na sua máquina, com o máximo de permissões fechadas, tipo o que você tem no diretório .ssh em todo Linux. E a chave pública é a que você pode divulgar pra qualquer um e é o que você cadastra na sua conta no GitHub por exemplo.

Mas como Alice e Bob conseguem autenticar a identidade deles? Ou seja, como Alice que sabe que Bob é confiável e vice versa? Aqui entra o papel de um Central Authority ou CA, como uma Certisign. Todo sistema operacional vem pré-instalado com as chaves públicas de autoridades confiáveis como a Certisign, Sectigo, Digicert, GlobalSign e outros. Quando você clica no cadeado do seu navegador e for no caminho da certificação, você vai ver no topo um desses CAs. Você confia neles porque vieram de alguém confiável. O Windows veio da Microsoft, a Microsoft confia nessas CAs e seu navegador vai confiar nelas também. Confiança é uma cadeia. Pra duas pontas desconhecidas confiarem uma nas outras elas precisam de um terceiro que ambos confiam primeiro. Esse é o princípio.

A Alice começa mandando antecipadamente a chave pública dela pra uma GlobalSign por exemplo. A GlobalSign vai usar a chave privada dela pra assinar a chave pública da Alice. A chave e a assinatura são armazenados num pacote especial, tipo um template, que é o formato X.509. De novo, quando você clica no cadeado em algum site com TLS ele abre várias informações, e é isso que vem dentro de um certificado X.509. Mais importante, nesse certificado está a chave da Alice assinada pela GlobalSign. Mas o que é essa assinatura?

Aqui vou simplificar de novo. Pra assinar alguma coisa com uma das chaves considere que as chaves são números. Digamos que a chave privada seja o número 10 e a chave pública seja o número 4. Você pega caractere a caractere da mensagem que quer assinar e multiplica ela por ela mesma 10 vezes. Se o caractere em binário é o número 2, vamos fazer 2 vezes 2 vezes 2 … 10 vezes. Lembram que no começo determinamos um módulo? Então se o resultado dessa multiplicação for maior que o módulo, aplicamos o módulo nele e essa é a mensagem encriptada. Agora pra decriptar pegamos essa mensagem e de novo, caractere a caractere multiplicamos ela por ela mesmo 4 vezes, que é a chave pública do nosso exemplo. Repetindo, se o número for maior que o módulo, aplicamos o módulo no número e o resultado vai ser a mensagem decriptada. Esse é o poder da aritmética modular.

Quantas vezes vamos precisar multiplicar cada caractere da mensagem? Lembram que eu falei que a gente gera chaves de 4096 bits? Isso é 2 elevado a 4096 que vai dar um número da ordem de 1233 dígitos. É um número astronômico e nenhuma calculadora nem sua linguagem favorita vai conseguir multiplicar. Mas calma, não vamos usar esse numerozão não. Sempre temos um módulo n, lembram? Vai ser o numerozão módulo n. Ainda assim é um numerozão, mas ordens de grandeza menor. E vamos multiplicar cada caractere da mensagem elevado a esse numerozão módulo n.

Voltando ao exemplo, é assim que a GlobalSign usa a chave privada dela pra assinar a chave pública da Alice, e ela coloca o resultado, que é a assinatura, num certificado X.509 que a Alice pode agora mandar pro Bob. O Bob confia na GlobalSign e tem a chave pública dela pré-instalada na sua máquina. Com isso a chave pública da GlobalSign ele pode decriptar a assinatura do certificado e achar a chave pública da Alice dentro. Com isso o Bob pode confiar na Alice. Agora o Bob pode enviar mensagens com segurança pra Alice. Pra isso ele pode usar a chave pública da Alice pra encriptar uma senha secreta e só a Alice vai poder decriptar com a chave privada que só ela tem. Pronto, como em Diffie-Hellman ambos os participantes possuem a mesma chave simétrica pra iniciar uma conversa segura encriptada. Podemos usar um AES pra trafegar mensagens encriptadas.

De tudo isso temos alguns problemas. No caso do Diffie-Hellman sozinho, ele não tem esse mecanismo de CAs e um terceiro de confiança pra assinar as chaves públicas. Portanto ela não tem como garantir a identidade dos participantes. Com STS ou no caso o RSA que é mais usado, ambos os participantes tem chaves pré-criadas e pré-assinadas, e eles podem fazer esse processo de aperto de mão, ou handshake, pra validar a identidade. Se o Bob consegue abrir a identidade da Alice com a chave pública da GlobalSign, sabemos que Bob confia na GlobalSign e a GlobalSign confia na Alice então, por consequência, o Bob pode confiar na Alice, e agora temos autenticação comprovada.

Mas porque usar AES pra encriptar a comunicação em vez de só usar RSA direto pra fazer a encriptação de tudo? Porque RSA é computacionalmente muito caro. As chaves são enormes, de 4096 bits, e tem esse processo ridiculamente pesado de multiplicação caractere a caractere pelo módulo da chave de 4096 bits. Seria inviável encriptar um stream de Netflix via RSA, por isso trocamos por AES, que sabemos que é rápido e até acelerado via hardware na CPU. Além disso temos outro problema, forward secrecy. Digamos que no futuro alguém de fato construa um computador quântico funcional de mais de 4000 qubits e execute o algoritmo de Shor. Agora toda comunicação do passado que usou as mesmas chaves assimétricas estão comprometidas. As chaves são a identidade das pessoas e elas raramente mudam. Então tudo que foi encriptado nelas poderia ser decriptado no futuro, por isso falamos que RSA não tem forward secrecy.

Mas Diffie-Hellman tem, porque a idéia é gerar novas chaves secretas a cada sessão. Então o que temos aqui? E se usarmos RSA pra gerar chaves assimétricas, certificados X.509 pra ter as chaves assinadas por uma CA que todos confiam? Com isso podemos autenticar a identidade dos participantes. Daí podemos usar Diffie-Hellman pra trocar segredos que nunca trafegam. E finalmente podemos usar AES pra fechar uma conexão segura e rápida. Em resumo bem resumido é isso que é um TLS ou o que chamamos de SSL faz! E em outras combinações é o que um SSH também faz.

No caso do SSH você usa a ferramenta ssh-keygen pra gerar o par de chaves, daí copia sua chave pública pro arquivo authorized_keys no servidor. Agora quando tentar se conectar no servidor ele pode autenticar sua identidade. No caso mais simples não precisa de um CA porque se a chave pública tá no servidor, alguém de confiança teve que colocar lá e isso já é suficiente pra servir de autenticação. É como funciona no caso do GitHub também.

Tanto no caso de TLS quanto SSH existe uma etapa que é a negociação dos ciphers. Eles não usam só um cipher. Ah é, acho que eu não disse mas chamamos esses algoritmos de criptografia de ciphers. Quando eles fazem o handshake o servidor envia uma lista de combinações de ciphers que ele suporta, o navegador vê sua própria lista e eles escolhem uma combinação que ambos suportam. Isso é um Cipher Suite. As combinações da lista são como esse TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384. Puta monte de letrinha, mas agora acho que você já reconhece quase tudo. TLS é o protocolo. ECDHE é Elliptic Curve Diffie-Hellman Ephemeral. Diffie-Hellman você já sabe, Elliptic Curve ou falar já já. RSA é o sistema autenticação com chaves assimétricas que acabei de falar. AES 256 é como a encriptação vai ser feita. GCM é o modo de execução do AES, chamado Galois Counter Mode, que também vou explicar já já. Finalmente SHA384 é o algoritmo de hashing HMAC que vai ser usado pra verificação de integridade dos dados.

Sobre GCM, você precisa entender que todo algoritmo de encriptação de chave simétrica tem customizações pra serem mais flexíveis. Por exemplo, um erro ao usar TripleDES é usar ele em modo ECB, que é o padrão. Nesse modo o initialization vector, ou IV é nulo e se eu rodar a mesma mensagem pelo TripleDES ele sempre vai gerar o mesmo valor encriptado. Mas adicionando um IV na operação do primeiro bloco vai acontecer uma avalanche porque o bloco seguinte usa o resultado do bloco anterior, como eu já expliquei no ataque de extensão de comprimento. Daí o resultado encriptado vai ser bem diferente pra toda vez que você mudar o IV. É meio como a idéia de um salt, quando geramos hashes pra gravar no banco, como também expliquei antes. Usar o IV é o modo chamado de CBC, que é o recomendado no TripleDES.

O AES também tem modo CBC. Sem entrar em muitos detalhes o modo CBC tem esse requisito de um bloco anterior influenciar o bloco seguinte, em cadeia. Por isso não tem como rodar em paralelo. Já o modo GCM é um tipo de contador e a característica é que você pode encriptar blocos em paralelo, tornando o processo mais rápido. Além disso, o GCM tem características que tornam a encriptação mais segura também, daí você pode usar uma chave menor do que em CBC, ou seja, em vez de usar chaves de 256-bits, pode usar uma de 128 e ter relativamente o mesmo nível de segurança. Você já deve ter entendido que quanto menor a chave, menos trabalho os algoritmos tem, são ordens de grandeza menos multiplicações. E em comunicação via TLS em sites seguros, você gostaria que a comunicação fosse segura mas também fosse rápida, por isso existe a opção de escolher AES GCM.

Agora, EC ou Elliptic Curve. Em resumo resumido, tamos falando literalmente de uma função que gera um gráfico parecido com este aqui do lado, isso é uma curva elíptica. Tem bastante matemática pra entender as propriedades dessa curva, mas uma importante é que se escolhermos dois pontos quaisquer dessa curva e traçarmos uma linha reta, ela sempre vai interseccionar num terceiro ponto. Um dos pontos da linha que escolhemos vai ser a chave privada e o ponto de interseção vai ser a chave pública. Sim, curvas elípticas são usados pra gerar chaves assimétricas. Lembram que no caso de RSA o processo começa escolhendo dois números primos gigantes e depois faz várias derivações pra chegar na chave privada? Aqui, de forma simplificada, é traçar uma linha em cima do gráfico e temos nossas chaves.

A propriedade importante é que as chaves geradas através de curva elíptica são mais resistentes a tentativas de fatorar os primos com Quadratic Siege e General Number Field Sieve. Elas são técnicas matemáticas pra tentar fatorar o numerozão nos primos correspondentes, sem recorrer só a força bruta. Mesmo sem computador quântico e Shor existem outras tentativas matemáticas pra tentar chegar na fatoração de primos. Não é prático e muito menos rápido, então acalmem-se, seus porns e nudes ainda estão seguros. A geração de chaves via curvas elípticas cria chaves ainda mais fortes do que o RSA normal consegue criar, graças à essas propriedades da curva.

Portanto ECDHE, o que é isso? Elliptic Curve Diffie Hellman Ephemeral. Lembram que eu falei que só Diffie Hellman sozinho pode sofrer ataque de man-in-the-middle? E precisamos usar algo como STS? No caso, a parte do Elliptic Curve substitui a necessidade de STS e providencia autenticação ao Diffie Hellman. E lembram como eu falei que as chaves do RSA sofrem de forward secrecy porque as chaves nunca mudam? Por isso tem o Ephemeral no nome desse, porque eles geram chaves assimétrica pra cada sessão e depois elas são descartadas. Assim, mesmo se um dia alguém quebrar as chaves que estou usando na minha sessão de Pornhub agora, o atacante não vai conseguir quebrar as sessões do passado, que usaram chaves efêmeras. Agora combinamos Diffie Hellman com chaves efêmeras via curva elíptica pra trocar segredos e podemos combinar com RSA pra garantir a identidade. E continuamos usando AES pra encriptar os dados em si com as chaves trocadas com Diffie Hellman. Agora sim, esse é mais ou menos o TLS, que você pode conhecer como SSL que todo site e aplicativo usa hoje em dia.

Chaves geradas através de curvas elípticas estão sendo adotadas cada vez mais. É isso que é usado no algoritmo do Bitcoin pra gerar suas chaves. A Apple usa no iMessage. Empresas como DNSCurve usam no DNS. E como vimos na cipher suite de navegadores, eles também suportam. E mesmo em SSH hoje recomenda-se criar chaves com ed25519. Aliás ele tem esse nome porque usa a curva 25519 que é uma curva elíptica. Se você viu essa recomendação em algum tutorial talvez não tenha entendido até hoje porque mandam usar ed25519 em vez de RSA, e porque o tamanho da chave é menor de uns 256 bits em vez dos 4096 bits que se recomenda pra RSA. Agora você já deve ter entendido. Como falei antes, as chaves de curva elíptica são mais fortes, e por isso podem ser menores. E sendo menor você também já sabe: muito menos multiplicações, o que significa uma encriptação mais rápida. Com o aumento na adoção na maioria dos lugares, hoje em dia, se for gerar chaves com ssh-keygen, escolha ed25519 em vez de RSA. O GitHub, por exemplo, suporta. Você vai notar como a chave pública que ele gera é bem mais compacta, justamente porque a chave é menor.

E agora você consegue entender todas as letrinhas que aparecem nos cipher suites dos navegadores. De qualquer forma, mesmo curvas elípticas ainda não são resistentes a um futuro ataque com Shor em computadores quânticos. Mas como eu expliquei no episódio anterior ainda estamos bem longe disso. E existem vários algoritmos novos na categoria de pós-quânticos que não dependem da dificuldade de fatorar primos.

Mesmo Curvas Elípticas continuam sendo suscetíveis a ataques de um possível futuro computador quântico também. Mas existe uma evolução disso que é o Supersingular Isogeny Graph. Parafraseando da Wikipedia, gráficos de isogenia supersingulares são classes de expansores de gráficos que aparecem em computação da teoria dos números e foram aplicados em criptografia de curva elíptica. Os vértices representam curvas elípticas supersingulares sobre campos finitos e as bordas representam isogenias entre curvas... Pronto, fodeu, eu não consigo nem ter um insight de como isso funciona. Mas, uma das propriedades importantes é que ele consegue ser mais seguro ainda com chaves ainda menores e é considerado pós-quântico. Quer dizer que é resistente aos algoritmos quânticos. Repetindo o que eu disse no episódio anterior, o mundo da criptografia não pretende ficar de braços cruzados esperando o computador quântico chegar. Povo tá antecipado. Em criptografia você sempre precisa estar um passo à frente.

E com isso fechamos o tema de conceitos básicos de criptografia. E eu não estou brincando, tudo que eu disse até aqui é o básico do básico de criptografia, especialmente se você tem interesse em criptanálise. Um dos campos da computação onde matemática é absoluto é criptografia. Mas mesmo sem saber a matemática toda, a intenção foi expor vocês a todos esses jargões e como eles se relacionam uns com os outros. Espero que agora você tenham uma noção melhor de onde cada jargão é usado, o que significam, quais os pontos fortes e fracos, e também porque você não deve sair tentando criar soluções que envolvem criptografia. Os ataques que eu falei aqui são todos “simples” pros especialistas e hackers. White hats e Black hats estão usando isso e coisas mais avançadas no arsenal deles. Novamente, eu tenho certeza que todo mundo ainda tem um monte de dúvidas, mas não deixem de discutir nos comentários abaixo. Se curtiram o vídeo mandem um joinha, compartilhem com seus amigos, assinem o canal e não deixem de clicar no sininho pra não perder os próximos episódios. A gente se vê, até mais.


[Akitando] #67 - Entendendo Conceitos Básicos de CRIPTOGRAFIA | Parte 1/2

DESCRIPTION

Errata:

Quando eu explico HMAC, claro que é uma simplificação mas eu mostro um exemplo de função chamada "hmac_sha1" e alguém pode achar que isso é a implementação completa, MAS NÃO É. Portanto NÃO copie do jeito que eu fiz, use a função de HMAC de uma biblioteca decente como a libsodium.

Esta semana é a parte 1 desta micro-série de 2 episódios sobre Criptografia.

No último episódio falei sobre o impacto da Supremacia Quântica sobre a criptografia atual, mas muita gente não tem muita noção do que realmente significa criptografia.

Então no episódio de hoje vamos começar com alguns conceitos muito básicos sobre encriptação simétrica, alguns algoritmos famosos como DES e AES, entender como acontecem alguns tipos comuns de ataques como dicionário ou extensão de comprimento, e entender o que são de verdades os hashes que programadores usam todos os dias sem saber.

Pule direto pras seções:

07:41 - guardar senha no banco 09:32 - ciphers de substituição 11:11 - Enigma 14:57 - DES 19:43 - AES 22:48 - Hashes 26:53 - Colisões 30:12 - Extensão de Comprimento 35:36 - Dicionários 37:35 - Aniversários

Links:

  • The Enigma Machine Online (https://cryptii.com/pipes/enigma-machine)
  • Can I break an uncracked Enigma code message? (https://www.theguardian.com/technology/2006/mar/02/guardianweeklytechnologysection2)
  • Cracking the Uncrackable: How Did Alan Turing and His Team Crack The Enigma Code? (https://www.scienceabc.com/innovation/cracking-the-uncrackable-how-did-alan-turing-and-his-team-crack-the-enigma-code.html)
  • Everything you need to know about hash length extension attacks (https://blog.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks)
  • Hash Length Extension Attacks (https://www.whitehatsec.com/blog/hash-length-extension-attacks/)
  • I have been Pwned? (https://haveibeenpwned.com)
  • Understanding the Birthday Paradox (https://betterexplained.com/articles/understanding-the-birthday-paradox/)
  • Shattered: We have broken SHA-1 in practice. (https://shattered.io)
  • Divide and Conquer: Cracking MS-CHAPv2 with a 100% success rate (https://web.archive.org/web/20160316174007/https://www.cloudcracker.com/blog/2012/07/29/cracking-ms-chap-v2/)
  • Secure your data with AES-256 encryption (https://www.atpinc.com/blog/what-is-aes-256-encryption)
  • Rainbow Tables: Your Password's Worst Nightmare (https://www.lifewire.com/rainbow-tables-your-passwords-worst-nightmare-2487288)
  • Hash Length Extension Attacks(https://www.whitehatsec.com/blog/hash-length-extension-attacks/)

SCRIPT

Olá pessoal, Fabio Akita

No último episódio eu tentei responder a pergunta se computação quântica tem o potencial de acabar com os esquemas de segurança que usamos hoje. Mencionei coisas como RSA ou AES. Eu imagino que não só iniciantes, mas mesmo desenvolvedores de software que já tem alguma experiência tem dificuldades de entender mesmo o básico sobre criptografia.

Cybersecurity é um tema gigante. Existem as tecnologias, as ferramentas e os processos. Segurança da informação ou Infosec é uma pequena parte de cybersecurity e foca mais na implementação e manutenção de processos e políticas, é onde você vê a maioria das certificações. Muito de segurança é uma questão de procedimentos.

Porém, essa parte me interessa muito pouco, pelo menos por agora. A idéia hoje é começar a explicar alguns dos jargões e conceitos pra que você se confunda menos quando estiver lidando com aspectos de segurança, em particular com criptografia. Vou repetir de novo porque é difícil de explicar a dimensão desse, é uma área gigante, com uma história longa, muita matemática, e você pode dedicar sua vida inteira estudando só isso e ainda não saber tudo. É tão grande na real que até este script, que é só um mero resuminho, ficou gigante e por isso tive que dividir em duas partes. Hoje vai ser a parte 1, semana que vem termino na parte 2. Peguem papel e caneta e se preparem, porque vocês vão precisar anotar bastante coisa pra estudar em mais detalhes depois.

E falando em segurança, agora é uma boa hora pra agradecer o patrocinador deste vídeo. Se você quer evitar que suas senhas sejam vazadas, ou evitar que o FBI descubra os sites de pornografia e bittorrent que você usa, devia estar usando uma VPN. E porque não usar NordVPN .... não não não, tô brincando, este episódio não é patrocinado pela NordVPN ou qualquer outra empresa, rola a vinheta!!

Se eu fosse chutar, eu diria que a maioria esmagadora dos programadores não pensa em segurança nem o mínimo que deviam. Sim, você mesmo aí assistindo. E eu também de vez em quando. Isso era assim 20 anos atrás e continua sendo assim hoje. O que mudou é que nos últimos 15 anos pelo menos criamos frameworks e práticas que todo mundo usa hoje meio sem pensar, e por causa disso muitos dos erros triviais foram evitados e você nem sabe disso. Por exemplo, um erro trivial e muito comum antigamente era codificar manualmente comandos SQL pra banco de dados numa string, concatenar o que o usuário mandava de um formulário, tipo de login, por exemplo, e mandar esse comando pro banco de dados. Como este exemplo aqui do lado. Mas se o usuário fosse minimamente esperto, nem precisa ser muito inteligente, sabendo que é assim que o programador faz, ele manda exatamente a string que se concatenada envia outros comandos arbitrários de SQL que o programador não estava esperando. Assim como nesse exemplo aqui do lado. Pronto, você tem um problema de SQL Injection na sua aplicação.

Praticamente todo framework web como Rails, Django, Laravel, ASP.NET e todos os outros hoje vem como algum tipo de ORM que é uma biblioteca de mapeamento objeto pra relacional, não só pra mapear tabelas com classes mas também fazer coisas como sanitização dos valores enviados por usuários. Daí a maioria de casos como SQL Injection são eliminados automaticamente. Além de SQL Injection a maioria dos frameworks competentes protege contra coisas como CSRF que é Cross Site Request Forgery, Session Hijacking, Replay Attacks e muito mais. Se não fizer no mínimo essas coisas, não é um bom framework.

Mas eu como disse antes. A maioria dos programadores não pensa em segurança. Daí você lê em algum post de blog, por exemplo, pra em vez de deixar o ORM gerar o comando SQL, se você escrever o comando manualmente e não usar nenhum ORM, pode ser que tenha mais performance. Pronto, você acabou de deixar de usar os mecanismos de segurança do framework pela promessa de performance. Isso é o mais comum. Programador é um bicho meio burro, e não devia ser. Nós devíamos ser de Ciência da Computação, cadê a ciência em ficar ouvindo promessas e implementando sem saber se funciona ou não? Tem hora que programador parece que tá escolhendo dieta da moda. Na dúvida é melhor assumir primeiro que se o framework guia você pra desenvolver de uma certa maneira deve ter razões pra isso. Especialmente se é usado por milhares de pessoas faz muitos anos. Entenda quais são essas razões antes de decidir não usar. Não estou falando de frameworkzinho hipster da moda que acabou de ser lançado. Bons frameworks são como bons vinhos, precisam de anos pra amadurecer. Só que aí programador iniciante tem mania de achar que sabe mais que as milhares de pessoas que gastaram milhares de horas homem amadurecendo o framework e decide: ah, vou fazer meu próprio framework porque eu sei fazer melhor. Pronto, você acabou de ganhar uma catástrofe esperando pra acontecer.

O trabalho de um programador é conseguir desenvolver alguma coisa o mais rápido possível, com a melhor qualidade possível. E qualidade significa não só funcionar como foi pedido como funcionar com a melhor performance, com a melhor escalabilidade e com a melhor segurança, e ainda tendo a melhor mantenabilidade. Só que é impossível ter 100% de todas essas características. Não existe almoço de graça. O trabalho de um programador é encontrar o meio do caminho entre todos esses parâmetros.

Esse balanço de tradeoff, ou de custo-benefício, não é simples. Aqui entra a experiência de ter trabalhado com outros programadores mais experientes e de ter visto atrasos, dificuldades de manutenção, buracos de segurança e gargalos de performance, na prática, em diversos projetos diferentes. Só porque alguma coisa "roda" não significa que ela funciona corretamente. A maioria dos cursos e tutoriais se preocupa em fazer alguma coisa "rodar", mas não se preocupa com todos esses outros aspectos: segurança, performance, escalabilidade, produtividade e mantenabilidade.

Por exemplo, nos anos 90 quando todo mundo ainda tava aprendendo como programar pra web, não havia frameworks decentes. Lógico, a Web comercial tinha acabado de nascer. Não tinha boas práticas bem definidas como temos hoje. Então digamos que você quisesse fazer um sitezinho com login, página de produtos, um checkout pra colocar cartão de crédito. Como se fazia? Simples: um formulário HTML que dava POST pra um script Perl ou PHP, que recebia a senha e o cartão e gravava direto numa tabela no banco. Ah sim, o banco e o site eram instalados na mesma máquina porque esses recursos custavam caro. E como quem programa site normalmente tinha e ainda tem pouco conhecimento de coisas como configuração de infra, então instalava tudo sem senha ou colocava a senha hardcoded no código. Desastre, lógico.

Na verdade eu não falei certo, isso não é só antigamente, todo iniciante começa fazendo exatamente desse mesmo jeito, não importa a linguagem. A diferença é que nos anos 90 todos mundo era iniciante ainda, mas com o tempo fomos evoluindo. Entenda isso: como iniciante você não tem como saber até onde dá pra ir, por isso você é iniciante. Mas não se preocupe, parta do princípio que só porque Você não sabe, não quer dizer que não existe. E só porque roda não quer dizer que você fez certo. O problema é um iniciante colocar um site em produção sozinho. Até hoje, quando eu clico em recuperar a senha, tem site que manda a minha senha, aberta, no meu e-mail. Em 2019, isso é ridículo. Uma coisa é você estar treinando e fazendo errado. Isso não tem problema, faz parte do aprendizado. Outra coisa é achar que só porque fez meia dúzia de tutoriais já sabe tudo e colocar seus usuários em risco. Isso é irresponsabilidade.

Como desculpa para iniciar alguns conceitos, vamos pegar o exemplo de guardar as senhas em plaintext, ou seja, em texto aberto, numa tabela do banco de dados, como eu disse que um amador faria se fizesse tudo do zero, sem usar nenhum framework. Vamos assumir que existe uma possibilidade real dessa tabela ser roubada por um hacker. Isso é muito mais comum do que você pensa, empresas gigantes de LinkedIn até marcas famosas como Sephora, sites de relacionamentos como Adult Friend Finder, games como Clash of Kings e muito mais que você pode procurar no site I have been pwned já tiveram dados dos usuários expostos. Nesse site aliás, você pode digitar seu e-mail e ver se sua senha já foi exposta. Toda tabela de senhas uma hora vai ser roubada, não é uma questão de “se”, é só uma questão de quando. Em segurança, nunca assuma que existe segurança, sempre parta do pressuposto do pior caso, que os dados vão ser expostos, mais cedo ou mais tarde. Seu trabalho é garantir que seja o mais tarde possível.

A segunda coisa que alguém poderia pensar pode ser, “ok, não tem problema, e se eu encriptar a senha antes de gravar no banco?” Primeiro vamos entender o que significa encriptação. Em criptografia normalmente estamos nos referindo à uma encriptação simétrica. De maneira bem simples, pense uma função onde passamos algum texto e um segredo como entrada e a saída é o texto bagunçado. Simétrico porque se passarmos o texto bagunçado de volta, com o mesmo segredo, revertemos de volta pro texto original. Isso é decriptação. As primeiras versões de encriptação era a coisa mais simples que você poderia imaginar: trocar uma letra por outra ou simplesmente dar shift e mover as letras pra frente ou pra trás como nos algoritmos Caesar ou ROT13. Esses normalmente são usados em cursos introdutórios de criptografia, tipo um hello world, por serem super triviais.

Um Caesar é basicamente um cipher de substituição. Primeiro escolhemos um valor de shift, por exemplo 2 pra ficar simples, esse é o segredo. Então toda letra A, dois pra frente, vira um C, toda letra B vira um D, toda letra C vira um F, sempre dois pra frente e assim por diante. A chave secreta é esse valor de shift 2. Uma forma de quebrar isso? Se você notar que se trata de algo parecido com um algoritmo Caesar, você pode tentar via força bruta que é tentar todos os 25 possíveis valores de shift já que o alfabeto só tem 26 letras, você pode facilmente tentar um a um até descobrir, no papel, em poucos minutos.

Outra forma é usar análise de frequência, por exemplo, se eu sei que a mensagem original é em inglês, as letras que aparecem com mais frequência na maioria das palavras são letras “E” ou “A”. Digamos que na mensagem encriptada a letra que mais aparece é “P”, e eu podia chutar que originalmente é um “E” e na verdade o shift foi de 11 letras. Se não funcionar, eu poderia chutar que é um “A” e que o shift foi de 15 letras. Daí em vez de tentar todas as 25 possibilidades na força bruta, só duas ou três tentativas já seria suficiente. Veja que mesmo nesse exemplo simples, podemos diminuir a dificuldade da força bruta de 25 tentativas pra dois ou três, quase 10 vezes mais rápido. Qualquer tentativa de quebrar uma encriptação, na pior das hipóteses é via força bruta, mas com criptanálise, como esse básico que mencionei aqui, ou seja analisando a mensagem encriptada, que chamamos de ciphertext, podemos usar matemática, mais precisamente estatística, pra diminuir as tentativas algumas ordens de grandeza.

O ROT13 é mais uma brincadeira, ele é um caso especial do Caesar com shift fixo de 13 letras. Aliás, o Caesar original foi usado pelos Romanos, por isso se chama Caesar e assim como o ROT13 também tinha um valor de shift fixo, acho que 3 ou 5. De qualquer forma, uma coisa que tem que ficar muito claro: o dado encriptado não pode compartilhar características com a mensagem original, porque via criptanálise eu conseguiria inferir alguma informação. Outro caso clássico ensinado como introdução em criptanálise é o famoso cipher Enigma usado pelos alemães na Segunda Guerra, que também era um cipher de substituição. Recomendo que dêem uma procurada em canais como Numberphile que explica ele em detalhes, mas o equipamento era tipo uma máquina de escrever onde cada letra que você teclava devolvia uma outra letra, qualquer uma das outras 25 letras do alfabeto, menos ele mesmo. E ele não substitui todo “A” pela mesma letra toda vez, cada A no texto vai virar uma letra diferente. A máquina é configurada conectando pares de letras no que se chama de plugboard, e configurando três rotores e um refletor que fazia o embaralhamento. Sem entrar em detalhes, se você souber a configuração desse plugboard, dos rotores e do refletor, daí você conseguia reverter a mensagem encriptada.

O grande erro dos alemães e o grande insight de Alan Turing foi notar que todo dia de manhã os alemães mandavam um relatório do tempo. E todo relatório começava com as mesmas palavras no topo da mensagem, tipo “tempo” que seria “Wetterbericht” em alemão e terminava com “Hail Hitler”. Daí ele entendeu que apesar das letras serem embaralhadas, a mesma letra nunca virava ela mesma. Então você conseguia chutar os pares de letras no plugboard e ir tentando decifrar letra a letra, mas uma hora dava errado porque o próximo par colidia com um que você já tinha achado que sabia. Então precisava começar de novo, mas como as letras nunca voltavam pra elas mesmas, a combinatória inteira de todos os pares que você testou podia ser descartada e só testar outra combinação inicial diferente dos pares que você já tentou.

E era isso que fazia a máquina que o Turing construiu, o tal Bombe. Se você já viu uma foto parecendo uma estante com vários rotores, a cada rotação ele vai tentando um espaço de combinações, não funcionando, descarta o espaço inteiro e tenta de novo, o que sobrar é a resposta certa. E ele conseguia achar a configuração em uns 20 minutos, de novo, dado esse insight de que a mesma letra nunca vira ela mesma e sabendo uma palavra que aparece em toda mensagem. Na força bruta, sem esses insights, só testando cada configuração possível do Enigma, um computador desktop dos dias de hoje ainda levaria de horas a dias pra conseguir decifrar. De novo, o que Turing e sua equipe conseguiram fazer, foi usar criptanálise pra diminuir drasticamente o espaço de tentativas.

Essa história toda foi pra explicar duas coisas. Uma função de encriptação simétrica com um segredo, no caso a combinação dos rotores, é construída de tal forma que via força bruta seria computacionalmente inviável. No caso do Enigma existem mais de milhões de milhões de milhões de possibilidades. Mas como ele tinha os defeitos que eu falei, mesmo nos anos 40, sem um computador potente como temos hoje, já foi possível derrotar o algoritmo em 20 minutos com rotores mecânicos. A segunda coisa: criar um algoritmo de encriptação robusto, resistente a criptanálise, é muito mais difícil do que parece. Se você não é um bom matemático teórico, nunca, jamais tente inventar seu próprio algoritmo. Sua melhor tentativa vai ser pior do que o Enigma.

Nos UNIX originais nos anos 70 o crypt foi implementado como uma variação de uma máquina de rotores, parecido com o algoritmo do Enigma. Algumas versões eram ainda mais fracas, e literalmente implementavam o Caesar que eu falei acima. O ROT13 não era brincadeira, ele chegou a ser usado de verdade. Essas implementações foram feitas numa época onde não havia a preocupação de hoje em criptografia. Feitos por gente como eu ou você, sem treinamento em matemática, era só pra deixar as coisas um pouco mais difíceis. Em breve o Caesar foi substituído no comando crypt por algoritmos melhores como o Data Encryption Standard ou DES.

O DES foi desenvolvido pela IBM e escolhido pelo NIST ou National Institute of Standards and Technology como o padrão de encriptação do governo americano nos anos 70, usando um segredo, ou chave, de 56-bits. Relembrando, significa um espaço de dois elevado a 56 ou 72 quadrilhões de possíveis chaves. Como vocês viram na história do Enigma e de algoritmos triviais de substituição, uma das fraquezas mais óbvias é encontrar um padrão ou relação estatística entre a mensagem original em plaintext e o ciphertext. Dentre as várias coisa que o DES implementou foram S-boxes ou substitution boxes. A partir daqui começa a entrar matemática mais complicada, coisas como funções de Bent, transformação Afim, transformada de Walsh e por aí vai. Mas basta entender que S-boxes são tabelas de substituição que causam o que Claude Shannon chama de confusão, tentando eliminar qualquer correlação ou relacionamento entre o plaintext da entrada e o ciphertext da saída.

Pra evitar a situação do “Hail Hitler” do Enigma onde conseguiríamos inferir alguma coisa do plaintext a partir do ciphertext, criou-se a idéia de quebrar a mensagem original em blocos. Digamos de 128 bits, passar por várias rodadas de transformação usando coisas como os S-boxes e usar o ciphertext desse bloco como entropia pro bloco seguinte. De tal forma que mesmo que o texto tenha trechos que se repetem, eles não vão ser encriptados da mesma forma, e assim no ciphertext final não temos como encontrar padrões. Mesmo assim, o mesmo plaintext aplicado no mesmo algoritmo sempre vai resultar no mesmo ciphertext e pra evitar isso também costuma se usar um Initialization Vector ou IV no primeiro bloco. Dessa forma, o plaintext com a chave secreta, variando esse IV, o ciphertext vai ser bastante diferente.

Além do princípio da confusão de Shannon ainda existe o princípio da difusão, ou seja, se mudarmos 1 bit no plaintext, pelo menos metade dos bits do ciphertext deveriam mudar de lugar. Diminuindo ainda mais a correlação do ciphertext com o plaintext. Enfim, não precisam decorar o que eu acabei de falar. A idéia é mais explicar como as coisas evoluíram desde o famoso Enigma.

De qualquer forma, o DES de 56-bits já foi quebrado. Uma das formas hoje de fazer isso é via FPGA ou Field Programmable Gate Arrays. Pense num chip genérico onde podemos desenhar circuitos e reprogramar esse chip pro tipo de circuito que quisermos. Se vocês assistiram o começo do meu vídeo sobre Supremacia Quântica eu rapidamente explico sobre portas lógicas e como podemos criar circuitos com essas portas. FPGAs é isso: reprogramação das portas lógicas, pra criar um hardware especializado. Enfim, hoje existem FPGAs que conseguem gerar da ordem de terachaves por segundo, no caso quase 800 bilhões de chaves por segundo e como eu falei que 56-bits dá um total de 72 quadrilhões de chaves, significa que dá pra quebrar o DES hoje em pouco mais de 26 horas, via força bruta. FPGAs não foram criados só pra isso claro, eles existiam antes, mas criptografia sempre vai usar o que tem de melhor pra estar um passo à frente.

O DES foi muito importante porque ele recebeu um escrutínio enorme. E como o governo americano estava usando, havia muita motivação pra entender como ele funciona, e obviamente como tentar quebrar. Lembrando, ele foi inventado nos anos 70. Esse sistema com FPGAs que eu falei foi criado em 2012. E mesmo assim leva 26 horas pra quebrar, não é instantâneo. Quando as primeiras teorias de como quebrar o DES começaram a aparecer, também surgiram formas de deixar o próprio DES mais forte e uma dessas formas é literalmente rodar o DES mais de uma vez. Mais especificamente, passar cada bloco da mensagem pelo DES três vezes, e isso é o que conhecemos hoje como TripleDES. Teoricamente ele é computacionalmente inviável de quebrar mas pouca gente usa TripleDES hoje. Isso porque o DES não é particularmente rápido e TripleDES, como você pode imaginar pode ser três vezes ainda mais lento.

Então o NIST comissionou um novo algoritmo e depois de um longo processo e diversos bons novos candidatos, eles escolheram o algoritmo Rijndael cujo nome é derivado dos nomes dos autores, Joan Daemen and Vincent Rijmen. Diferente do DES que usa chaves de 56-bits, o Rijndael usa chaves de 128, 192 ou 256 bits. Agora que vocês sabem que o sistema de FPGA mais rápido de 2012 demorava 26 horas pra percorrer o espaço de 56-bits, lembram que eu expliquei no episódio anterior que cada mais um bit que você adiciona duplica o espaço? Uma chave de 256-bits é 2 elevado a 256 que dá um número tão astronômico que tamos falando de um número com 77 casas. O quadrilhão que eu falei acima tem só umas 15 casas. O algoritmo usa essa chave pra derivar outras 10 chaves, usa os tais S-boxes que eu falei, passa cada bloco por 10 a 13 rodadas de transformações mais complicadas que o DES. E com tudo isso ele ainda é três vezes mais rápido que o DES.

O algoritmo Rijndael foi batizado pelo NIST como Advanced Encription Standard ou AES. Mas as técnicas de criptanálise evoluíram também. Hoje em dia sem saber que algoritmo ou que chave é usada, existem análises que checam quanto tempo tá levando cada trecho do algoritmo, o consumo de energia em cada etapa, a radiação eletromagnética e muito mais pra conseguir mais dados pra diminuir as possibilidades de tentativas, já que força bruta pura num espaço de 256 bits é computacionalmente inviável. E uma das formas de mitigar isso que se chama de ataques de side-channel é que hoje em dia o processador implementa o AES direto em hardware, pra evitar que implementações de software possam ser analisadas quando rodam.

Já faz 10 gerações atrás que a Intel implementa essas instruções no processador, desde uma antes da Sandy-Bridge. A AMD também implementa desde o Jaguar. E é o AES-256 que você provavelmente usa se tem partição encriptada. Com tudo isso, ela é extremamente rápida, o suficiente pra encriptar e decriptar dados do seu HD em tempo real, e é absurdamente segura. Eu concluí no episódio de Supremacia Quântica, que nem um computador quântico seria capaz de quebrar o AES, ele só diminuiria a força bruta em uma ordem de grandeza, mas o espaço computacional é tão gigante que ele continua inviável de quebrar mesmo assim.

Eu queria dizer que encriptação simétrica de ciphers de bloco são funções que recebem um plaintext, um segredo e um initialization vector e cospe um ciphertext seguro. Mas isso não explica o que realmente significa ser seguro, então resolvi fazer essa tangente. Acho que vale a pena ter uma noção do que significa algo ser encriptado com AES. Mas eu não demonstrei como funciona essa geração de chaves a partir da chave de 256 bits, como ele gera as tabelas, os S-boxes, as várias rotações e como isso aumenta a confusão e difusão segundo Shannon. Se tiver interesse existem centenas de documentações e videos aqui no YouTube mesmo explicando o processo.

Agora, existe uma outra categoria de algoritmos que é comumente confundido com encriptação e são as funções criptográficas de hash ou message digest, literalmente funções que digerem mensagens. Diferente de encriptação simétrica como o TripleDES ou AES, onde você pode reverter o processo e decriptar do ciphertext pro plaintext, algoritmos de hashing são o que chamamos de one-way, direção única, ou seja, irreversíveis. Toda função de hashing pega um plaintext de qualquer tamanho e cospe uma saída de tamanho fixo. Por exemplo, o antigo MD5 sempre vai cuspir uma saída de 128 bits, representado como uma string de 32 caracteres em hexadecimal. Outro que vocês devem conhecer é o SHA1, ou Secure Hash Algorithm 1, que cospe digests de 160 bits representados em uma string de 40 caracteres em hexadecimal.

Nos primórdios da internet, com conexões instáveis e pouco confiáveis, pra garantir que o download de um arquivo não veio corrompido era usado o antecessor do MD5, o MD4 pra comparar os hashes original e o do arquivo downloado. Um MD5 é basicamente o MD4 com mais uma rodada. Já já vou explicar o que é uma rodada.

Ambos MD5 e SHA1 tem seus usos, mas são considerados inseguros hoje em dia. No lugar deles deve ser usado SHA2 ou o SHA3 que foi aprovado pelo NIST em 2015, a partir do algoritmo chamado Keccak. Vocês devem ter notado que o NIST tem uma nomenclatura meio padrão, AES pra encriptação é o Rijndael, SHA3 é o Keccak, isso pode confundir. Mas fora dos meios de criptografia, poucos ouvem falar em Rijndael ou Keccak e você ouve mais AES ou SHA. Mas enfim, como funciona e pra que servem algoritmos de hashing?

Eu disse que a função recebe uma entrada de qualquer tamanho, seja uma string de senha, seja um arquivo PDF de contrato, seja um arquivo binário do instalador de algum programa, e sempre vai cuspir uma saída de tamanho fixo, de 128 bits no caso do MD5 ou 160 bits no caso do SHA1. Pra fazer isso um MD5, por exemplo, você divide o binário do arquivo e vai processando em blocos de 128-bits. É um cipher de bloco, só que em vez de preservar os blocos numa cadeia, aqui ele usa o processamento do bloco anterior em cima do próximo bloco, sobrando sempre um bloco de tamanho fixo. Em cima de cada bloco é aplicado alguma computação, e é aí que os algoritmos diferem pra ganhar as características desejadas de segurança. Pra simplificar, imaginem que o texto original é este blob em binário aqui do lado, de 3 linhas só.

Agora vamos quebrar esse blob em blocos de 10 bits em vez de 128, de novo, pra simplificar. De forma extremamente ingênua, vamos só aplicar um exclusive OR ou XOR que eu expliquei no vídeo anterior, um bloco de cada vez. Um XOR da primeira linha com a segunda linha. E agora o resultado com a terceira linha, mas temos um problema, a terceira linha não tem 10-bits, pra poder executar o XOR precisamos completar ela, e pra isso fazemos o que se chama de padding. Se não estou enganado um MD5 continua o que falta colocando primeiro um bit 1 e o resto com zeros. Pronto, agora fazemos o último XOR. E no final, temos um único bloco de 10-bits e isso seria uma função de hashing de 10-bits que é extremamente inseguro porque eu só usei um mísero XOR. Claro que o MD5 faz mais coisas por bloco, mas só pra vocês terem uma noção de como a partir de uma entrada de tamanho arbitrário, chegamos numa saída de tamanho fixo.

Novamente, não implementem uma função de hashing assim como eu mostrei. Um MD5 usa tabelas pré-computadas, divide cada bloco de 128 bits em quatro sub-blocos de 32-bits, performa várias operações além de XOR, como AND, OR, e mais, e depois de vários rounds de operações concatena os sub-blocos de volta nos 128-bits e repete pra cada bloco. É um pouco mais complicado e mesmo assim o MD5 e o SHA1 não são mais considerados seguros. Eles tem pelo menos dois problemas, sofrem de ataque de colisão e ataque de extensão de comprimento.

Pra entender colisão precisamos entender algumas das características desejáveis em funções de hashing. Primeiro, gostaríamos que o hash que ela cospe, que é um número binário, seja difícil de distinguir de uma função que cospe números aleatórios. Ou seja, não seria bom que a saída se concentrasse próximo de certos números e sim que ela tivesse uma distribuição normal. Como a frequência de números jogando dados. Nunca se concentrando só num número. Vamos dar um exemplo de uso. Digamos que eu tivesse a tarefa de organizar centenas de livros em dezenas de estantes numa biblioteca. Como organizar?

A primeira coisa que vem à cabeça seria talvez organizar em ordem alfabética por título ou autor. Porém, rapidamente você vai notar que tem mais livros que começam com certas letras do que outras, por exemplo, que começam com a letra A do que com Z, faz de conta. Mesmo se eu organizasse assim, eu ainda ia ter que ficar caçando em qual estante começa e em qual prateleira começa qual letra. Em vez disso poderíamos numerar cada posição em cada estante, digamos que cada estante comporte 200 livros e temos 20 estantes então temos espaço pra 4000 livros. Temos 1000 livros pra organizar, e poderíamos passar o título numa função de hashing e ele nos devolve um número entre 1 e 4000. É quase 12-bits, portanto seria uma função de hashing de 12-bits.

Agora, o que seria o ideal? Que cada título devolvesse um número não repetido, porque não seria legal se muitos livros caíssem na mesma posição, certo? Se caíssem elas entrariam em colisão. Não dá pra colocar dois livros no mesmo lugar físico nesse nosso exemplo. Essa é uma das coisas que é desejável numa função de hashing: que existam poucas ou o mais próximo de zero colisões. Mas justamente uma das fraquezas do MD5 e do SHA1 é que descobriram que se você pegar o título do livro e cuidadosamente alterar o título colocando digamos alguns zeros no final do título, uma hora chegaríamos no mesmo hash de outro título, criando uma colisão.

Por que isso é ruim no mundo real? Uma das utilidades de uma função de hashing é ser uma impressão digital. Digamos que eu tenha um contrato de 100 páginas em Word. Como eu sei que depois que eu transmiti o arquivo pra você via e-mail, ninguém interceptou o e-mail, alterou o contrato e mandou uma cópia adulterada? Simples, antes de enviar o contrato eu passo ela num MD5 que vai me cuspir uma impressão digital de 32 caracteres. Eu passo o arquivo pra você e te digo qual é o hash. Daí você pode passar o contrato pelo MD5 do seu lado e comparar com o meu hash e se bater sabemos que é o mesmo arquivo.

Porém, se a função é fraca contra ataques de colisão, alguém no meio do caminho poderia adulterar o contrato pra te foder, mudar as palavras cuidadosamente pra tornar o contrato ruim pra você. Só que isso iria alterar o hash. Mas aí quem interceptou, sabendo da fraqueza do MD5, faz pequenas alterações como adicionar ou tirar uma vírgula, um ponto, alguns espaços e é possível chegar no mesmo hash do arquivo original. Aí você recebe o arquivo que foi cuidadosamente adulterado, roda ele no MD5 e ele te dá o mesmo hash do arquivo original. E você agora vai confiar que esse contrato é o válido e vai assinar sem ler. Isso é um ataque de colisão.

E o tal ataque de extensão de comprimento? Aqui vou simplificar bastante mas eu deixei o procedimento completo nos links na descrição abaixo. Digamos que você tenha feito um site pra fazer download de arquivos privados, um mini Dropbox. Você faz o upload de um arquivo e o site devolve uma URL com uma chave especial na URL. Muita gente faria assim, pegaria o nome do arquivo, concatenaria com algum segredo que fica no servidor e devolveria o hash feito com MD5 ou SHA1. Pra baixar de novo o arquivo, você precisa mandar a mesma URL com o nome do arquivo e esse hash. Em particular esse uso de hash pra autenticação é o que se chama de MAC ou Message Authentication Code. Toda vez que você usa um hash pra autenticação damos esse nome de MAC.

Enfim, digamos que você é um usuário malicioso querendo baixar outros arquivos do servidor diferente desse que deu upload. Digamos o arquivo /etc/passwd. Só que não temos o segredo do servidor pra concatenar com esse nome de arquivo e gerar um novo MAC, então como podemos fazer? Lembram como o hash é calculado no meu exemplo simples com XOR? O string é quebrado em blocos de 128-bits no caso do MD5 ou 160-bits no caso do SHA1, o último bloco provavelmente não vai ter o tamanho total então fazemos padding adicionando o bit 1 seguido de zeros até completar o tamanho do bloco. Daí fazemos várias operações em vários rounds em cima de cada bloco e usamos esse resultado em cima do bloco seguinte. O hash de cada bloco, no caso do SHA1, é a composição de 4 sub-blocos de 32-bits, que é concatenado pra gerar o hash intermediário até o último bloco que ele tinha. Daí vamos pro bloco seguinte, que tem nosso valor adulterado. Entendem? Tudo que a função de hash precisa pra processar o bloco seguinte é o hash dos blocos anteriores. Ou seja, sem saber o texto original passada pra função, se soubermos o hash, sabemos como processar novos blocos seguintes só adicionando o padding de zeros antes do nosso valor malicioso.

Pra gerar o tal MAC que é o hash com SHA1, o servidor concatena um certo segredo com o nome do arquivo, faz o padding até o tamanho do string ser divisível por 160 bits e vai aplicando o SHA1 um bloco em cima do outro. Sem saber o segredo, se eu souber aó o tamanho do segredo, eu posso fazer o padding de zeros manualmente a partir do nome do arquivo que eu fiz upload e iniciar um novo bloco com o nome do arquivo que eu realmente quero. E eu vou conseguir gerar um novo MAC mesmo sem saber o segredo, porque o hash que eu já tenho é tudo que a função precisa pra processar o próximo bloco. É meio complicado de visualizar mas imagina que sabendo só o hash que é esse MAC do arquivo original, eu posso configurar o estado da função do SHA1 pra continuar a partir desse ponto, como se ainda tivesse mais um bloco pra terminar de computar, que é o bloco malicioso que eu concatenei no final. O problema é que o hash gerado pelo MD5 ou SHA1 me dá todas as informações que preciso pra continuar a fazer ele rodar pra novos blocos e gerar hashes válidos.

Se não entenderam, revejam esse trecho, ou acreditem que só de saber o hash eu posso adicionar novos blocos e gerar um novo hash válido como se a mensagem original tivesse esse bloco adicional desde o começo. Mas tem um porém, justamente pra evitar essas coisas você concatena um segredo no começo da mensagem original, no caso o nome do arquivo. Mesmo que eu acredite que esse procedimento funcione, como eu vou saber o tamanho do segredo, que é importante pra saber quantos zeros eu preciso colocar de padding? Simples, digamos que a aplicação que eu estou tentando hackear é baseado num projeto open source, tipo um Wordpress ou Magento da vida. Basta ir no GitHub e ver que tamanho de segredo está sendo usado no código fonte e testar. De novo, eu não preciso saber o segredo, só o tamanho da string do segredo.

É por isso que existe HMAC, que talvez vocês já tenham esbarrado em algum tutorial. Pra ajudar a evitar ataques de colisão e ataques de extensão de comprimento como expliquei acima. Em vez de só aplicar o hash em cima segredo mais a mensagem. Depois de fazer esse hash, você aplica a função de hash de novo concatenando o segredo com o hash. Ou seja, rodamos a função de hash duas vezes uma em cima da outra concatenando o segredo nas duas vezes. Isso é um HMAC ou hash-based message authentication code. Você usa isso quando quer garantir que o conteúdo original da mensagem não foi adulterado. Algoritmos mais novos como SHA3 não precisa fazer esse procedimento, ele sozinho já é um HMAC.

Estão vendo como as coisas não são tão simples quanto vocês pensam? Todo mundo pensa que ah, basta eu concatenar um salt ou segredo numa string antes de aplicar o hash e ele vai já vai ser magicamente seguro. Por isso eu repito que vocês não devem subestimar segurança e ficar tentando fazer soluções de criptografia na mão. O que determina a força de um algoritmo não é só o tamanho da chave, é como você usa o algoritmo. E falando em subestimar, ataques de extensão de comprimento compromentem hashes usados como MACs ou seja, como métodos de verificação e autenticação, como no exemplo de download de arquivos. Mas e hashes usados pra obfuscar senhas, como no caso da tabela de senhas roubado?

Um procedimento padrão é receber a senha de um usuário no cadastro e nunca guardar a senha aberta e nem encriptar. Recapitulando, porque se a senha de encriptação for roubado, você consegue abrir 100% de todas as senhas da base. Você tem um ponto único de falha muito perigoso. Por isso a próxima idéia que muita gente teve foi fazer hash das senhas antes e gravar só esse hash no banco. Daí quando o usuário for fazer login, você faz o hash da senha que ele digitou e compara com o hash do banco, se bater, quase com certeza sabemos que é a senha certa. Parece promissor, não parece? Mas só guardar o hash da senha ainda é muito fraco.

A maioria dos usuários usa como senha palavras fáceis de lembrar. Incluindo coisas toscas como 12345678 ou teste123. Ou dados que estão abertos no seu cadastro, como data de aniversário ou algo parecido. Via força bruta, testando todo string possível, você levaria anos pra encontrar uma única senha. Mas os hackers não fazem força bruta. E se você pré-calculasse o hash de todas as palavras mais usadas de um dicionário? Agora você sai comparando o hash da tabela de senhas roubada com a tabela de hashes do dicionário. Numa base de dados grande como de uma rede social, com milhões de pessoas, você com certeza ia achar centenas ou milhares de senhas assim.

Ataques de dicionário ou ataques com rainbow tables usam tabelas pré-computadas de hashes. Lembrando que a possibilidade de colisões num algoritmo de hash é tecnicamente pequena mas não é zero. Duas senhas diferentes podem acabar colidindo no mesmo hash. Lembram do exemplo dos livros e estantes? Mesmo usando algoritmos que não tem as fraquezas de colisão do MD5 ou SHA1 como o SHA2, ainda assim sempre existe o risco do paradoxo do aniversário, já ouviram falar? Vale contar isso se você nunca ouviu antes porque novamente, o que chamamos de “senso comum” normalmente falha perto da estatística.

Imagine uma sala de aula, qual a chance de duas pessoas fazerem aniversário no mesmo dia? Você podia pensar que seria perto de zero, afinal uma sala de aula tem só 30 alunos, e existem 365 dias no ano. Porém, é mais frequente do que você pensa. Pra calcular é simples e vou deixar links nas descrições abaixo, mas pense assim. Em vez de tentar calcular a chance de duas pessoas terem o mesmo aniversário, é mais fácil calcular primeiro a probabilidade de ninguém na sala ter o mesmo aniversário. Então a primeira pessoa pode fazer aniversário qualquer dia do ano, então ele tem 365 dividido por 365, ou seja 1. A segunda pessoa só pode cair em 364 dos dias restantes, então 364 dividido por 365. A terceira pessoa só pode cair em 363 dos 365. E assim por diante. Multiplicamos todas as probabilidades, pros 30 alunos. Agora, fazemos 1 menos essa probabilidade pra ter a probabilidade de duas pessoas terem o mesmo aniversário, sabe qual o resultado? Numa sala de aula de 30 pessoas a probabilidade é de uns 70%! Tá bem longe de zero isso. Colisões são estatisticamente mais frequentes do que gostaríamos, é um fato da matemática. E é esse um dos perigos com algoritmos de hash, o fato que colisões são probabilisticamente possíveis.

Eu levanto esse fato porque muito programador usa hashes de algoritmos como SHA256 achando que eles sempre vão ser únicos e por isso poderiam ser usados até como identificadores únicos. Mas isso tá errado. Por acaso, a distribuição estatística de um message digest se comporta parecido com um gerador de números pseudo aleatórios, mas eventualmente haverá colisões como eu acabei de explicar. Por isso que existe outra coisa se você precisa de números únicos, que são os Universally Unique Identifiers ou UUID, ou a implementação da Microsoft que são os Globally Unique Identifiers ou GUID. Por acaso eles são também números de 128-bits, que é o mesmo comprimento da saída do MD5, representados como hexadecimais de 32 caracteres. Por isso a “cara” de um UUID é meio parecido com a saída de um MD5. UUID também tem chance de ter colisões, mas eles são desenhados pra essa probabilidade ser realmente quase zero. Quando acontecem, e já aconteceu algumas vezes, é considerado um bug na implementação.

Aliás, falando em strings parecidos, também não confundir com o novo padrão IPv6 que também é um endereço de 128-bits, e adivinhem, representados com 32 caracteres hexadecimais. Portanto, pra quem nunca viu nada disso, um endereço IPv6, um identificador único UUID ou GUID, e um hash de MD5, tem mais ou menos a mesma “cara”, sendo todos números de 128-bits, representados com 32 caracteres hexadecimais. Mas eles são gerados de formas diferentes e tem usos completamente diferentes.

E com isso eu vou terminar por hoje num cliffhanger! Exatamente neste ponto estamos na metade do assunto e o resto já está escrito. Mas agora é uma boa hora pra rever o que eu expliquei. Peguem suas anotações, façam mais pesquisa nos pontos que mais se interessaram e estudem bastante porque o próximo episódio vai ser tão denso quanto este. Não vou nem perguntar se vocês ficaram com dúvidas, porque até eu ainda tenho muitas dúvidas em muitas coisas. Mas não deixem de comentar abaixo, se curtiram o vídeo mandem um joinha, compartilhem com seus amigos, não deixem de assinar o canal e clicar no sininho pra não perder a parte 2. A gente se vê semana que vem, até mais.


[Akitando] #66 - Entendendo Supremacia Quântica

DESCRIPTION

Errata:

Em 8:20 eu somei errado 🤦‍♂️ a soma devia dar 108722. Akita não sabe fazer soma kkkkk

Na história do xadrez eu falei errado 230 toneladas em vez de 230 milhões de toneladas. Mas mesmo assim seriam 2 mil anos de produção pra chegar nos 18 sextilhões :-D

Hoje vai ser mais um vídeo realmente longo porque eu quero dar uma repassada por tudo que você precisa saber pra entender o que é computação quântica e finalmente conseguir ter uma idéia do que significa a "Supremacia Quântica" que o Google diz que conseguiu alcançar semana passada.

Vou aproveitar pra retornar à introdução da computação pra quem é iniciante ter uma idéia do que de fato é um computador, onde estamos na evolução, qual a relação com coisas como GPUs, como as coisas estão evoluindo e onde computação quântica se encaixa nisso.

E enquanto eu falar sobre computação quântica vamos tocar nos assuntos que muitos esperam ver como a resposta da IBM refutando o que o Google clamou, os algoritmos quânticos que podem quebrar os algoritmos de criptografia e o que isso tudo realmente significa.

E mesmo com mais de 1h de vídeo eu ainda não vou ter tocado no pico do iceberg. Eu pesquisei vários lugares e escolhi alguns links pra vocês estudarem também mas isso deixou a descrição tão longa que o YouTube! reclamou do comprimento kkk então eu compilei tudo num Gist:

  • Links de Referência (https://gist.github.com/akitaonrails/9c8acc9f182b8d7fd591d724fdb6a387)

SCRIPT

Olá pessoal, Fabio Akita

O episódio dessa semana tá atrasado porque eu ia falar de um assunto mas nos quarenta e cinco do segundo tempo eu resolvi mudar o assunto e reescrever o script do zero. Parte do assunto original era eu rapidamente falar sobre alguns conceitos básicos de criptografia e segurança. Acho que todo mundo pelo menos ouviu falar no termo Quantum Supremacy que o Google anunciou semana passada, alguns dizem até que o preço do Bitcoin despencou 1000 dólares entre dia 23 de outubro por causa do medo de que o Bitcoin estivesse com os dias contados. Claro, tudo exagero da mídia e o preço do Bitcoin já subiu de novo mais 2000 dólares em cima da baixa.

Na verdade, criptomoedas é o menor dos seus problemas se real supremacia quântica representasse uma ameaça hoje. Basicamente toda encriptação assimétrica, de tudo, teria chances reais de ser quebrada hoje. Então, se o Google atingiu a tal Supremacia Quântica, tamos ferrados então? Todos os nudes do seu whatsapp encriptado podia vazar então? Calma, não suem ainda.

A mídia não especializada, como sempre, quer vender notícias, então eles precisam colocar títulos dramáticos, exagerados e às vezes simplesmente errados. Drama, notícia ruim e fake news vende. Notícia boa não vende. Eu não sou especialista em computação quântica, pouca gente é, mas já que eu já falei de criptomoedas no canal e justamente eu estava pensando em falar sobre o tema de segurança, acho que aproveitar o embalo da supremacia quântica pra falar sobre a fundação da computação pode ser uma boa.

Primeiro, vamos definir Supremacia Quântica, que é um termo que foi cunhado pelo físico John Preskill em 2012 onde um computador quântico deveria ser capaz de executar um cálculo, qualquer cálculo na real, mais rápido do que conseguiríamos fazer no computador mais rápido que existe hoje. Agora se segurem que por 2 minutos eu vou falar um monte de jargões que você certamente não vai ter a mínima idéia do que eu tô falando, mas vamos lá:

O Google construiu um computador quântico de cinquenta e quatro qubits. O teste que foi usado pra medição é um tipo de gerador de números "verdadeiramente" aleatórios. O paper do Google diz que esse computador conseguiu executar esse cálculo com cinquenta e três dos cinquenta e quatro qubits durante 3 minutos e 20 segundos e a parte da supremacia é que o Google afirmou que levaria 100 mil computadores convencionais o equivalente a mil anos pra executar o mesmo cálculo, bem longe do que seria prático e portanto eles atingiram a definição de supremacia do Preskill. Mais especificamente o paper mostra que o experimento implementou um circuito com um depth ou profundidade 20, com 430 gates de 2 qubits e 1.113 gates de 1 qubit.

Essa notícia causou furor na mídia e a IBM entrou no drama dizendo que o resultado é bom mas não é tão dramático assim. E que se usar o Oak Ridge Summit, o super computador da IBM que ocupa o espaço de 2 campos de basquete com seus 250 petabytes de espaço em disco, eles poderiam escrever os 9 quadrilhões de amplitudes em disco, quebrando a premissa de escrever o vetor inteiro em uma simulação de tipo Schroedinger pra uma simulação de Schroedinger-Feynman fazendo trade-off de espaço por tempo. E fazendo isso, em vez dos mil anos que o Google disse que ia levar, o IBM Summit poderia simular o mesmo cálculo em algo próximo de dois dias e meio, que é uma ordem de grandeza muito menor.

Hahahaha, se você entendeu 100% de tudo que eu falei até agora, parabéns, que diabos você tá fazendo aqui neste canal? Agora, se como eu você entendeu só uma parte ou praticamente nada, eu quero tentar explicar em mais detalhes. Preciso começar pedindo desculpas porque o script de hoje não é 1% do que eu gostaria de escrever. Porém, ele seria 100 vezes mais longo e levaria semanas pra eu escrever. Se vocês forem nas descrições do vídeo abaixo e ver a quantidade de coisas que eu linkei pra vocês, não é metade do que eu estudei pra escrever esta versão. Eu peguei emprestado muita coisa de muitos canais muito bons que eu vou mostrando ao longo do video. Sério o assunto é denso demais pra só em uma semana eu virar expert no assunto. E mesmo ainda nem comecei, é um assunto extremamente fascinante que eu provavelmente vou continuar estudando daqui pra frente.

Antes de entrar na parte sobre computação quântica, como eu sei que tem muitos iniciantes assistindo, eu vou fazer uma tangente em computação clássica e apresentar algumas coisas que todo programador já deveria saber, todo cientista ou engenheiro da computação já sabe, mas se você é autodidata ou fez um curso que não é de análise de software ou sistemas de informação, talvez nunca tenha visto ou só viu muito por cima. Não se preocupem não vou me alongar demais nisso, só relembrar alguns conceitos muito básicos.

Vamos lá, sendo hiper simplista. Toda a revolução da computação começa com um conceito muito simples. Um bit. Pra qualquer pessoa normal que aprendeu na escola a calcular tudo com sistema numérico de base 10 é bem estranho ter intuição sobre outras bases. Mas base 10 é o mais intuitivo, temos 10 dedos nas mãos. Um computador não tem 10 dedos, ele tem só 1 dedo, que antigamente nos primeiros computadores poderia ser uma válvula. Na verdade qualquer coisa que consiga guardar dois estados serve como bit. Pode ser uma lâmpada, ligado representa 1, desligado é 0. Pode ser um interruptor, ligado e desligado. Assumindo que conseguimos criar um sistema com vários desses dispositivos em série, podemos armazenar um dado em binário.

Por exemplo, se tivéssemos oito interruptores em série e quiséssemos armazenar o número 42 em base 2 você vai dividindo o número 42 por 2 e vai acumulando o resto, e a sequência dos restos é o número em base 2 ou binário. Então no caso 42 é 1 0 1 0 1 0. Uma sequência de 8 bits é o que chamamos de um byte. Em um byte podemos representar a combinação de dois elevado a 8 números, que dá um máximo de até duzentos e cinquenta e seis números. Cada um bit que adicionamos dobra a quantidade de números que podemos representar. Então se fosse uma sequência de 9 bits seria 2 elevado a 9 que é 512.

Existe uma lenda indiana ou algo assim sobre o inventor do tabuleiro de xadrez. Ele mostrou pro imperador da Índia que ficou tão impressionado que ofereceu uma recompensa. E o tal criador disse que só queria um grão de arroz pra começar e pra cada casa do tabuleiro ele queria que a quantidade fosse dobrando. Então dois grãos pra segunda casa, quatro grãos pra terceira casa, oito grãos pra quarta casa e assim por diante, acumulando os das casas anteriores claro, até a casa sessenta e quatro do tabuleiro. O imperador pensou como qualquer um assistindo aqui pensaria: ah, isso é uma miséria de arroz e aceitou.

Mas daí o tesoureiro voltou pro imperador alguns dias depois falando: fodeu, você prometeu uma quantidade de arroz absurda. Seria mais de 18 sextilhões de grãos. O Brasil inteiro produz mais de 230 toneladas de arroz. 100 grãos de arroz pesa 2 gramas e meio. Isso dá 9 bilhões de grãos. Ou seja, se eu não errei a conta, o Brasil sozinho levaria 2 bilhões de anos pra produzir os 18 sextilhões que o indiano ficou devendo. Detalhe, a Terra tem 4 bilhões e meio de anos. Não é intuitivo se você não está acostumado, mas esse exponencial rapidamente. Contei essa lenda pra vocês terem uma noção na cabeça sobre grandes números, dimensões que você não tem muita referência no dia a dia. Mas se um dia te oferecerem um milhão de dólares ou, um centavo todo dia, sendo dobrado todo dia durante um mês, escolha a segunda opção.

Mas voltando, a representação binária tem algumas vantagens na hora de representar cálculos aritméticos. Relembrando aritmética clássica, somar dois números inteiros é simples. Você coloca um em cima do outro e vai somando o número de cima com o de baixo, se der maior que 10 você tem o “vai 1” pra cima do logo à esquerda, você agora tem três números pra somar, e vai fazendo isso. Com binário é exatamente a mesma coisa.

Agora, num mundo onde você tem bits, como fazer uma máquina binária calcular, por exemplo, a soma de dois números binários? Em cima de cálculo binário você tem toda uma gama de operações que podem ser feita com zeros e uns. Somar é uma operação mais complexa que pode ser montada com outras operações mais simples. Se você é programador já sabe algumas delas como o operador binário AND ou OR. AND é simples, se você tem um e um vai dar um, se for um e zero vai dar um, se for zero e um vai dar um e se for zero e zero vai dar zero, ou seja, AND é sempre um com exceção se for os dois zeros. O OR é diferente, é tudo zero com exceção se os dois valores forem um. Mas existe uma operação mais básica, é o que chamamos de NAND ou NOT-AND onde zero e zero é um, zero e um e um e zero é um, e só se for um e um vai dar zero, é o oposto do AND.

O NAND é interessante porque você pode construir todas as outras funções booleanas como AND, NOT e OR só com o NAND se quiséssemos, é uma propriedade tanto do NAND quanto do NOR que é o NOT-OR. Essas operações podem ser construídas com transistores num circuito, o que chamamos de gates ou portas lógicas em eletrônica e lógica booleana. Quando falamos de lógica, é aqui que começamos. Toda porta lógica tem uma ou duas entradas e uma saída. Pense em portas como funções que tem argumentos e retorna resultados. Podemos compor uma porta com outra gerando novas portas, assim como fazemos com funções. Uma porta NAND é feito com uns 4 transistores, uma porta NOT com 2 transistores e assim por diante. Se você ligar a saída de uma porta NAND com a entrada de uma porta NOT você tem a porta AND por exemplo.

Se você estudou lógica booleana conhece essas operações. Se não, estude, isso é super fundamental. Vamos voltar pra soma de números. Todo mundo sabe somar números decimais. Em soma binária, toda vez que tem um e um, daria dois, mas daí não cabe no bit, então “vai um” pra esquerda e você coloca zero no bit atual. Quando é zero e um ou um e zero, daí a soma é um. E quando é zero e zero é zero mesmo. Soma em números binários é a mesma coisa. Se ignorar a parte do vai um, o que acabamos de descrever é a tabela verdade da porta XOR ou eXclusive OR então podemos começar por essa porta.

Pra fazer o que se chama de half-adder ou somador parcial, podemos adicionar uma porta AND pra receber os mesmos dois parâmetros e se for um e um ele vai devolver um, e esse é o “vai um”, daí repassa os dois valores pra porta XOR e essa é a soma parcial. Agora podemos conectar duas somas parciais com mais algumas portas como XOR e pra não ficar muito longa a explicação terminamos com um full adder ou somador completo, mas de 1 bit. Pra somar números de 4 bits, conectamos 4 full adders e assim por diante.

Pense que um CPU acessível de hoje em dia tem bilhões de transistors. Assim você começa a entender como de princípios muito simples é possível programar coisas mais complicadas. A ciência da computação estuda e cria as fundações. Coisas simples como propriedades associativa e comutativa dos valores numa porta lógica. No dia a dia a gente assume que se alguma função funciona pra um ou dois valores, deve funcionar pra tudo. Mas em matemática a gente é mais formal que isso: eu preciso conseguir testar pra todos os inteiros, por exemplo, de menos infinito até mais infinito e garantir que funciona, ou quais exatamente são as exceções. Não basta dizer “é óbvio que vai funcionar”. Daí temos todas as premissas ou axiomas pra começar a provar tudo que tem em cálculo, álgebra e assim por diante.

Tudo que usamos em termos de cálculo de números binários obedece regras, e podemos demonstrar que a matemática sempre consegue prever o comportamento físico das portas lógicas montadas com transistors por exemplo. Daí temos certeza que num circuito desenhado corretamente com 100 transistors, isso vai escalar pra mil transistors ou bilhão. É difícil de explicar isso mas pense na matemática como uma linguagem, ou seja, um vocabulário e uma gramática pra descrever com precisão o comportamento físico desses materiais. Matemática eu gosto de pensar como a melhor linguagem pra descrever a natureza e o universo, nossas línguas faladas simplesmente são primitivas demais e não tem palavras suficientes pra descrever tudo, por isso é difícil de explicar muita coisa com precisão. Entenda matemática e você começa a enxergar o comportamento preciso da natureza. Guarde esse conceito.

Do transistor vamos pra portas lógicas e daí pra circuitos mais complicados, daí chegamos na arquitetura básica de um CPU e coisas como registradores, contador de programa, e operações mais complexas que são as instruções de máquinas que programamos em Assembly. Com esse Assembly podemos criar linguagens mais fáceis de programar como um C, por exemplo, e com C podemos criar todas as outras linguagens que conhecemos hoje. Cada nova camada vai facilitando o uso do computador, com o trade-off de diminuir a eficiência do uso da máquina. Mas como temos algumas dezenas pra bilhões de transistors, podemos nos dar ao luxo de tornar as linguagens mais próximas do programador e mais longe da máquina.

Pra gente que é de ciências da computação, tem toda uma área que nós só aceitamos que existe e funciona, que é eletrônica. Por isso não vou nem raspar a ponta do pico desse iceberg hoje. A gente comumente ouve falar que antigamente existia válvula e depois inventaram os transistores e a revolução dos computadores nasceu. Existem décadas de história que envolve a experimentação de novos materiais e técnicas de fabricação. A gente não usa materiais como silício e cobre do nada, foram anos de experimentação. Transistores em conceito fazem a mesma coisa que uma válvula ou uma lâmpada fazem. Só que no caso em escalas de milionésimos de milímetro. Aplicando uma voltagem dá pra criar uma barreira magnética que impede a passagem de corrente, e isso chamamos de zero, ou quando ele libera essa barreira e daí passa corrente e seria um. Os transistores mais comuns hoje são os MOS ou MOSFET que é sigla pra metal-oxide-semiconductor field-effect transistor, que é o que eu acabei de explicar.

Os primeiros circuitos integrados tinham da ordem de meia dúzia de diodos, transistores, resistores e capacitores. De lá pra cá os processos de fabricação e materiais mudaram bastante e o processo atual do que se chama de VLSI ou very large-scale integration que é a idéia de criar chips ultra densos com milhões ou bilhões de transistores é o CMOS ou complementary MOS. Um Intel Core i7 de oitava geração tem da ordem de mais de 2 bilhões de transistores. Os processos de fabricação buscam soluções pra diminuir cada vez mais o tamanho dos transistores ao mesmo tempo que tentam diminuir o desperdício de energia. Novos processos de fabricação e materiais continuam sendo pesquisados, então este formato de computador não vai deixar de existir tão cedo.

Agora precisamos fazer um salto de conceitos. Primeiro vocês precisam entender teoria quântica e mecânica quântica. Estamos ainda numa fase onde a palavra “quântico” entrou no zeitgeist, todo mundo repete a palavra quântico e ninguém sabe o que diabos é um quantum. Se você quiser fazer qualquer porcaria parecer da hora, só gruda a palavra quantum do lado e já era. Não ajuda muito que Richard Feynman, um dos melhores físicos de todos os tempos, soltou a famosa frase dizendo “se você acha que entende física quântica, você não entende física quântica”. E isso levou à interpretação errônea de que nem os cientistas entendem física quântica e isso deu passe livre pra qualquer idiota achar que a interpretação de qualquer pessoa é tão boa quanto dos cientistas. E por sua vez isso abriu caminho pras bobagens que o povo de New Age e os atuais coaches quânticos repetem que nem gralhas.

Eu já disse no outro vídeo, opinião é que nem bunda, todo mundo tem um. Opiniões não tem valor por si só, só o que pode ser cientificamente demonstrado tem valor, todo o resto é capim. O meu capim vale tanto quanto o seu. E aqui vou afirmar categoricamente: a teoria quântica e mecânica quântica definem muito bem tudo o que funciona e o que não funciona. Hoje sabemos como fazer os cálculos quânticos com a linguagem da matemática, e diversos experimentos comprovam os resultados. A matemática é a única linguagem que deve ser usada. Qualquer outra coisa que qualquer um de New Age ou coach quântico inventar é 100% bullshit, bobagem, e devem ser totalmente ignorados sem nenhuma chance de absolutamente nada.

A próxima coisa que um coach desses vai dizer é que eu não consigo provar que ele tá errado na interpretação dele. E essa é outra forma de bullshit. O ônus da prova é de quem faz a afirmação, sempre. E prova não é uma historinha, ou uma “evidência” que mostra alguma correlação. O famoso “ah, mas eu conheço casos” que é a mesma coisa que bullshit. Uma evidência não comprova uma hipótese. Todo experimento científico precisa ser independentemente testado e funcionar todas as vezes. Não basta funcionar uma vez e chamar de “prova”. Funcionar uma vez é coincidência. Teorias como de Darwin, Einstein, Schroedinger e outros são consideradas sólidas porque ao longo das décadas, dezenas ou centenas de outros cientistas repetiram os experimentos de forma independente e toda vez os resultados batem, sem ambiguidade. Toda vez que o experimento demonstra uma falha, por menor que seja, a matemática inteira precisa ser revista. É que nem seu código, não adianta parecer que funciona só uma vez, na sua máquina. Coloca em produção e deixa eu ver se realmente funciona. Se quebrar, precisa arrumar o código. É o mesmo processo.

Os comportamentos quânticos só funcionam no mundo das partículas subatômicas. Quando tamos falando de coisas como eléctrons ou fótons. Elas não se manifestam nos materiais do nosso mundo macroscópico. Nós não somos “feitos de energia”, nós não temos “consciência coletiva”, não existe isso de energia do pensamento positivo ou qualquer outro bullshit assim. Não é assim que a ciência funciona. No mundo do nosso tamanho, assuma física newtoniana. No mundo cosmológico, assuma relatividade especial de Einstein. E no mundo subatômico, use mecânica quântica. É só isso. Os estudos sobre física quântica ainda não estão completos, mas na prática isso não importa muito porque os efeitos quânticos são reais e experimentalmente demonstráveis e mensuráveis. Então existem dois campos de pesquisa: a procura do “porquê” os efeitos se manifestam da forma como se manifestas e o “como” podemos usar esses efeitos na prática. Um é a física teórica e a outra é a física aplicada.

Pensa assim, faz séculos que os exploradores olharam pros céus e entenderam que era possível usar as estrelas pra se localizar e navegar na direção certa. Ninguém daquela época sabia exatamente o que eram aquelas coisas brilhantes no céu, mas você podia saber exatamente onde elas estariam a cada noite. Era possível calcular a posição das estrelas mas não era possível ainda entender o que elas eram. Eram deuses? Eram alienígenas? Eram velas incandescentes penduradas numa redoma? Levou séculos até descobrirmos que são gigantescas bolas de gás incandescentes sofrendo reações de fusão nuclear formando um corpo celestre com a força da gravidade. Física quântica é um pouco parecido, nós sabemos navegar, calcular com precisão, mas só não descobrimos ainda o que realmente são. Qualquer tentativa de interpretação vai cair na categoria de velas penduradas numa redoma.

Essa é uma das partes que eu estou mais me coçando pra tentar explicar mas eu acho que levaria semanas pra eu conseguir compilar todas as histórias do século XX inteiro em pesquisa de teoria quântica e o video teria horas e horas. Então eu vou me conter por agora. Pra quem já se interessou é a parte onde começaria falando como Max Planck encontrou a constante de Planck, como Einstein usou isso pra descobrir o efeito fotoelétrico e postular a idéia de que a luz é formada por pequenos pacotes de energia, a menor quantidade possível, que é o fóton ou quantum e, dois Nobels depois, inicia a revolução dessa nova física chamada mecânica quântica. Daí vai passar por todos os nomes que em algum momento você já ouviu falar como Werner Heisenberg, Louis de Broglie, Erwin Schrödinger, Max Born, John von Neumann, Paul Dirac, Enrico Fermi, Wolfgang Pauli, e muitos outros, até chegar na controversa interpretação de Copenhagen de Niels Bohr.

Então só acreditem que existe mais de um século de pesquisas, diversas histórias dramáticas e brigas como os papers de Einstein Podolski e Rosen e o gato de Schroedinger, o teorema de John Bell. E como o Einstein, que sem querer deu início à essa nova física, até o fim tentou demonstrar como essa teoria era incompleta, e toda tentativa dele de quebrar a teoria no final acabou colaborando pra novas descobertas que acabaram resolvendo os paradoxos que ele mesmo levantou. Como falei antes, neste momento ainda não sabemos tudo, de fato, mas é como eu falei sobre as estrelas. Hoje temos o suficiente pra aplicar o que se sabe em problemas reais. O porquê elas funcionam é o que ainda vai levar décadas ou séculos pra sabermos. Foi assim com a física clássica. Qualquer interpretação como a hipótese de existir um multiverso, é só isso, uma teoria por mais que nosso fandom pela Marvel fosse adorar se isso fosse verdade.

Neste ponto eu recomendo 4 canais que eu gosto bastante e que já gravaram bastante material explicando a parte da física, a parte da matemática e as aplicações. Vou deixar os links nas descrições abaixo mas são as playlists sobre mecânica quântica dos canais MinutePhysics, PBS Space Time e Infinite Space, o excelente 3Blue1Brown e o reconhecido Veritasium. Tem outros ainda mas pra começar, esses são suficientes, são várias horas de diversão. Mas não dá pra resumir tudo isso em 15 minutos, infelizmente.

Caindo em computação quântica, vamos começar do começo, como você constrói um qubit? Pegando emprestado a explicação do canal Veritasium, por exemplo, com átomos de fósforo em um cristal de silício bem do lado de um transistor. Esses átomos tem um spin, como você já deve ter aprendido no colegial em física eletromagnética. Spin pra cima e spin pra baixo como chamamos. Pra resetar a orientação do spin desses átomos pro estado de menor energia, spin pra baixo, você precisa de um campo elétrico de um supercondutor. Pra mudar o spin você aplica energia, e não precisa de muito, só o calor ambiente já seria suficiente pra fazer esses átomos ficarem mudando de spin que nem doido. Por isso você coloca esse aparato num ambiente com hélio líquido, completamente isolado, pra chegar em temperaturas de frações de 1 Kelvin, quase perto do zero absoluto. E agora, pra mudar o spin de forma controlada de um átomo de cada vez, você envia microondas numa frequência específica.

Agora, existe uma interação entre um portal de elétrons do transistor de silício que reage com o átomo de fósforo e aplicando frequências específicas é possível medir esse átomo. Mais do que isso, podemos mudar e medir o spin do núcleo de um átomo de fósforo. O spin desse núcleo pode ser medido e visto num osciloscópio, indiretamente através da interação com um elétron. O núcleo é mais estável e mais isolado que um elétron e consegue conservar esse spin por um período de tempo considerável e com isso conseguimos armazenar um valor 1 ou 0 nele, como num transistor tradicional. Esse núcleo é o qubit. Veja o vídeo do Veritasium pra mais detalhes mas o importante é dizer que já conseguimos manipular um núcleo do átomo de fósforo individualmente pra encodar bits.

Agora você vai precisar acreditar no que eu falei antes: no um século de evolução da física quântica e os efeitos que já sabemos que funciona e são controláveis. Esse qubit obedece as leis da mecância quântica e conseguimos realizar algumas operações nele. Uma delas é mudar o spin pra cima ou pra baixo de forma controlada e que podemos ler depois. Então sabemos como encodar valores binários numa cadeia de qubits. Podemos fazer mais. Podemos colocar o qubit num estado de superposição. Esse é o estado que todo mundo coça a cabeça pra entender. É o tal estado do gato morto vivo de Schroedinger. O gato tá vivo ou morto dentro da caixa? Isso é um exercício teórico e tem mais a ver com a substância radioativa dentro da caixa que seria o gatilho pra matar o gato do que o gato em si. Mas falando puramente de forma simbólica, a resposta é que ele não tá nem vivo nem morto, o que significaria mais exatamente um estado 100% vivo ou 100% morto. Em matemática não existe “mais ou menos”, é sempre um estado definido nem que seja probabilístico. Então o gato, ou mais precisamente o átomo da substância radioativa, está num estado probabilístico 50% vivo e 50% morto.

“Mas isso quer dizer o que?” e a resposta é: não importa, esse é o estado seja lá o que na realidade isso signifique. Não existe um estado desse na nossa realidade macroscópica por isso nos falta vocabulário pra descrever, mas matematicamente é um estado bem definido. Era o grande problema do Einstein e Schroedinger, de que é difícil conciliar a realidade macroscópia com a matemática quântica, por mais que na matemática faça sentido. É como se fosse um terceiro estado, que seria uma range de probabilidades entre os dois estados. Só faz sentido dizer se é vivo ou morto depois de medirmos e ver se depois de abrir a caixa o gato de fato está vivo ou de fato está morto. Mas não é que o estado já existia e a gente só não estava conseguindo ver. Esse estado não tem localidade e nem causalidade, é um estado não determinístico real. Verdadeira aleatoriedade. Antes de abrir a caixa ele é indefinido. Por isso é melhor evitar metáforas com o mundo real o quanto possível. Sem a matemática pra descrever, vira essa história mal contada de gato morto ou vivo indefinido.

Esse conceito é importante porque toda vez que ouvir falar de superposição vai ouvir esse palavreado de “medir” e “colapsar”. O que não é intuitivo pra mim e acho que pra muita gente é, Beleza! Digamos que eu engula esse estado completamente aleatório que pode ser 50% 1 ou 50% zero. Como eu faço cálculos com isso? Existem outros mecanismos que você precisa entender. O próximo é o também controverso entanglement. Você pode colocar dois qubits em superposição e depois em entanglement. Daí quando você medir um deles - que vai causar o colapso do seu estado pra 1 ou 0 -, quando medir o outro qubit ele vai estar no mesmo estado.

Se já era difícil colocar na cabeça que uma partícula pode ter um estado indefinido antes de medir, entanglement é acreditar que duas partículas podem estar correlacionadas e se manter assim mesmo se separadas. A propriedade do entanglement é que uma vez correlacionadas eu posso separar as duas e “transportar” pra muito longe. No exato instante que eu medir uma a outra automaticamente vai tá no mesmo estado quando for medido. Ou seja, se antes de medir elas tavam numa superposição de 50% de chance de ser um ou 50% de chance de ser zero, quando eu medir a primeira e der um, a outra partícula só tem 100% de chance de ser um.

Mas cuidado, isso não é uma comunicação, ou seja, não existe a possibilidade da primeira partícula, depois que medimos e colapsamos seu estado tenha enviado essa informação pra segunda partícula. Ou seja, não existe possibilidade de um Skype quântico. Essa foi uma controvérsia que Einstein odiava porque se fosse esse o caso a informação teria que viajar mais rápido que a velocidade da luz. E nada viaja acima da velocidade da luz, é impossível. Mas então, pelo senso comum, as partículas já teriam que ter “combinado” antes de serem separadas qual seria o estado final no colapso. Mas é o que o teorema de John Bell já provou que não é o caso, ou seja, existe um experimento que prova que não haveria como as partículas terem qualquer informação do estado final antes de medirmos.

Novamente, essa é uma das partes onde não vamos chegar em lugar nenhum se ficarmos especulando se a estrela é uma vela pendurada numa redoma no céu ou o quê. Ainda não temos informação suficiente pra afirmar nada. Só podemos afirmar que é exatamente isso que acontece. Já foi testado exaustivamente, de forma independente. Por exemplo, em 2017 um experimento chinês foi capaz de colocar dois fótons em superposição e entanglement e enviar um deles via um laser pra um satélite a mais de 1200 km de distância e realizar a medição e o colapso. Vários desses experimentos já foram feitos. Vamos só aceitar que superposição e entanglement existem. Nos vídeos dos canais que eu recomendei eles explicam em mais detalhes sobre isso também.

Aliás, só pra finalizar os efeitos não intuitivos. Uma vez tendo essas propriedades você acaba tendo teletransporte também. - O que, ah, vai se fuder, tá de brincadeira, isso é Star Trek agora puta que pariu - ufff Pois é! É possível um efeito de cut and paste ou “recortar e colar”. É possível teletransportar o valor de um qubit pra outro qubit de forma destrutiva, ou seja não é um clone, é uma teletransporte. Por exemplo, Alice e Bob podem ter dois qubits pré-entangled. Daí Alice prepara um terceiro qubit e faz entanglement com o dela. Ela prepara um payload qualquer e ao medir os dois é feito o teletransporte desse payload - cujo valor é destruído nesse instante - pro do Bob através do pré-entanglement que eu falei que já tinha entre os qubits da Alice e do Bob, que vai funcionar como um cabo de rede entre os dois.

Pronto, fodeu, ficou bem abstrato isso. Vamos ver na prática como isso poderia funcionar. Pois bem, esse é o diagrama do circuito de três qubits em entanglement. Aqui temos os qubits de Alice e Bob sendo colocados em entanglement. Agora temos a preparação do payload no terceiro qubit. Agora a Alice faz o entanglement do qubit do payload com o qubit dela. E finalmente teletransportamos o payload pro Bob. É literalmente como se fosse um recortar e colar, sem um intermediário fazendo isso. E no passo final o Bob pode verificar que de fato o payload chegou nele.

Isso que você tá vendo é como representamos um circuito quântico. Cada linha representa um qubit e temos 5 deles. Cada um desses quadradinhos é uma quantum gate ou o equivalente a uma porta lógica de um computador clássico que eu expliquei lá no começo. Temos portas como X ou Y ou Z que são portas de Pauli. Basicamente o X faz um bitflip como uma porta lógica NOT. O H é um Hadamard que se eu entendi direito coloca o qubit em superposição. O mais comprido que puxa uma linha entre dois qubits com um sinal de mais no meio é uma porta chamada CNOT ou controlled NOT, e na prática ele coloca os dois qubits em entanglement. No final onde temos um ícone que parece um medidor, e é justamente isso, a operação que faz a medição do qubit não é uma porta lógica porque um requerimento pra ser uma porta lógica é que toda operação quântica seja reversível. Esse princípio é matematicamente importante, mas pra gente basta entender agora que dizer costuma ser o fim do circuito.

Esse diagrama é uma representação visual de um código, que é escrito no formato OpenQASM. E o que diabos é isso que eu tô mostrando? Esse é o site IBM Q Experience. Eles tem uma plataforma online com documentação explicando alguns conceitos e eles tem online tanto um editor pra você montar seus próprios circuitos quânticos visualmente ou via código, e rodar tanto num simulador quando em computadores quânticos de verdade! Claro que versões fraquinhas de no máximo 5 qubits e alguns com taxa de erro elevada. Mas dá pra brincar um pouco, é como se você estivesse montando um circuito eletrônico num simulador com o CircuitLab onde você pode montar circuitos tradicionais com portas lógicas, como a máquina de somar que mostrei no começo.

Se você tem interesse em molhar os dedos no básico, pode baixar o Qiskit que é um projeto open source feito em Python, onde você pode escrever programinhas em OpenQASM como mostrei antes e rodar localmente na sua máquina ou conectando no simulador da IBM. Uma coisa que talvez você não sabia é que é possível simular qubits num computador tradicional. Obviamente você não consegue simular quantos qubits quiser, porque vai ficando exponencialmente mais pesado. Mas alguns poucos qubits dá. E pra aprender, com cinco qubits já dá pra testar muita coisa. Por exemplo, em computação clássica você viu que com poucos transistores você consegue montar um somador completo de 1 bit com portas lógicas tradicionais. Daí vai adicionando vários pra conseguir somar números de 8 bits e assim por diante. Mas você sabe que se funciona com 2 ou 3 bits, vai funcionar pra 8 ou 16 bits. Você consegue escrever manualmente, no papel, as tabelas verdade pra verificar.

Em computação quântica, é mais complicado. A representação na mão é feita com notação de álgebra linear. No diagrama você vê os qubits representado por essa notação estranha de um pipe, um número e um bracket de maior. Essa é a notação de Dirac pra vetores. E toda a álgebra linear, multiplicação de matrizes e tudo mais funciona. Essa portas quânticas que falei, é basicamente representado com uma multiplicação de matrizes com o vetor que são os qubits. Do ponto de vista da ciência da computação você pode visualizar as máquinas de estado dos qubits como valores num círculo - se estiver representando só com inteiros - onde cada valor é uma probabilidade da superposição. Na realidade a matemática é usando números complexos e os valores caem num espaço de Hilbert. Lembra que eu falei que em superposição você tem valores num espaço probabilístico? Pra entender a quantidade, pense em pontos na superfície de uma esfera por exemplo.

O Hello World de programar um computador quântico é justamente colocar uma porta Hadamard pra colocar um qubit em superposição e uma porta CNOT pra fazer o entanglement com outro qubit e depois medir pra colapsar o estado. Como são dois qubits temos quatro possíveis estados zero zero, zero um, um zero, e um um. Igual nas tabelas verdade das portas lógicas que expliquei no começo. Um programa quântico a gente não roda só uma vez, precisa rodar várias vezes e fazer um histograma dos resultados de cada estado. Se fosse um sistema completamente aleatório, você esperaria que saísse qualquer um dos quatro estados um quarto ou 25% das vezes. Mas como estamos com um sistema em entanglement, se rodarmos várias vezes, no exemplo, os estados zero zero e um um saem mais que os outros. Justamente porque se eu medir o primeiro e der zero, o outro em entanglement tem que dar zero também, mesma coisa com um. Os resultados zero um e um zero que aparece é devido a erro no sistema, se fosse perfeito deveriam aparecer zero vezes. Vou falar de erro depois, guardem isso.

Eu vou dizer que me falta intuição matemática - falta de prática mesmo - pra entender direito o que tudo isso significa. Intuitivamente eu consigo ter uma noção. Na prática você tá lidando com variáveis aleatórias, porém via essas portas lógicas e coisas como entanglement, você consegue influenciar coisas como a amplitude e a fase, na prática, você influencia as probabilidades dentro da superposição. Daí você roda esse programa centenas de vezes e plota num histograma, onde o sinal for mais forte é onde a barra do histograma vai ser maior, ou seja, é o resultado que aparece mais vezes. Numa metáfora de pobre, como se fosse uma moeda onde você pudesse influenciar o resultado durante o giro dela, e aí começa a ser mais cara que coroa por exemplo. Então um insight que eu não tinha é que um programa quântico não dá uma resposta absoluta como estamos acostumados em programação com portas lógicas, ou qualquer coisa em cima disso, como linguagens de programação tradicional.

O cálculo com qubits trás um resultado dentro de um espaço de probabilidades. Nós manipulamos a aleatoriedade do princípio da incerteza de Heisenberg pra conseguir manipular um espaço de possíveis probabilidades. Cada qubit pode estar num range de resultados diferentes em superposição, e com entanglement podemos conectar um qubit com vários outros qubits, formando um campo de influência. Pense como várias ondas como na física acústica, uma onda causando interferência na outra. Dois picos em duas ondas, uma amplifica a outra, duas ondas inversas, uma cancela a outra. E se configurar essas portas quânticas corretamente você começa a ter a possibilidade de fazer cálculos com vários estados em superposição, ao mesmo tempo, o que leva à conclusão que computadores quânticos poderiam resolver problemas em uma ordem de grandeza menos tempo; problemas que hoje, em computadores tradicionais, levaria tempo exponencial.

Pra entender um pouco mais da matemática dessa representação eu recomendo começar com estes vídeos da série da PBS Infinite Space falando sobre a matemática por trás dos qubits. E depois ver essa apresentação da Microsoft de computação quântica pra cientistas da computação, onde é explicado passo a passo como fazer cálculos com os vetores e matrizes na mão com algumas portas quânticas mais simples e como, com isso, você consegue implementar um algoritmo muito simples, como o Deutsch Oracle.

Falando em algoritmos, os primeiros exercícios como o Deutsch Oracle, Deustch Josza, Simon começaram a aparecer no começo dos anos 90. Lembram que falamos bastante sobre a natureza da função de onda e como ela descreve o possível espaço de resultados? Se falamos de ondas você que é engenheiro vai se lembrar de Fourier. Fourier pode ser usado pra centenas de aplicações que vai de processamento de sinais até bioengenharia. Recomendo assistir o vídeo do 3Blue1Brown que explica visualmente como a transformada de Fourier funciona pra separação de ondas e coisas como limpeza de barulhos, especialmente de barulhos com períodos constantes, como o barulho do motor de avião, que é a base de como cancelamento de barulho ou noise cancelling avançado funciona em fones de ouvido bons. E existe uma implementação chamada tranformada quântica de Fourier. Ela não é exatamente o equivalente do Fourier tradicional, mas serve para vários casos.

Em particular ela permite o algoritmo de Shor, inspirado no algoritmo anterior de Simon. Esse é o temido algoritmo que chamou a atenção do mundo pra computação quântica e causou furor quando foi publicado pela primeira vez nos anos 90. Se não me engano o primeiro algoritmo com potencial uso real que poderia mudar a forma como usamos computadores. O algoritmo de Shor consegue, em alguns casos, cerca de trinta e sete porcento das vezes, conseguir fatorar números inteiros nos seus fatores primos. Isso é extremamente inviável de se fazer num computador tradicional. Pegue dois números primos gigantes, digamos de dois mil e quarenta e oito bits de comprimento. Multiplique os dois. Todos os computadores do mundo podem gastar bilhões de anos pra conseguir encontrar os dois fatores primos originais.

O algoritmo não é perfeito. Você não consegue passar um número gigante e ele automaticamente cospe os fatores pra você. Na realidade você começa com um chute e com as propriedades de aritmética modular e o teorema de Fermat você consegue ir chutando números melhores que, em quase 40% dos casos pode chegar na fatoração. Eu fiquei pensando se descrevia o algoritmo aqui ou não mas acho que vai muito além do que precisa pra este episódio. O canal MinutePhysics junto com o 3Blue1Brown fizeram alguns vídeos muito bons que explicam passo a passo como é o processo pra encontrar esses fatores. E o canal PBS Infinite Space tem outro um pouco mais pesado nos conceitos que vale a pena assistir depois pra entender os passos.

Sem entender aritmética modular não dá pra entender como funciona isso de quebrar a fatoração. Mas entenda que o processo que existe hoje é basicamente brute force, testar um número atrás do outro. Com as características de superposição, entanglement, e aplicando a transformada quântica de Fourier, é possível fazer o cálculo que levaria centenas de anos em alguns minutos. Hoje em dia o algoritmo de Shor já foi executado em computadores quânticos de verdade e conseguiu fatorar os números 15 e 27 que é o atual recorde. Tá beeeem longe ainda de um número de 2048 bits. A razão toda de porque todo mundo ficou histérico com a afirmação do Google ter alcançado Supremacia Quântica é a possibilidade deles conseguirem usar o algoritmo de Shor na prática com mais qubits.

Recapitulando o que eu disse no começo.”O Google construiu um computador quântico de cinquenta e quatro qubits.” - E agora você sabe o que é um qubit. - “O teste que foi usado pra medição é um tipo de gerador de números "verdadeiramente" aleatórios.” - e agora você tem uma intuição do verdadeiramente aleatório por causa da superposição. Diferente de pseudo-aleatório que é o que usamos em computadores tradicionais onde você produz números que estatisticamente parecem aleatórios mas na realidade não são. “O paper do Google diz que esse computador conseguiu executar esse cálculo com cinquenta e três dos cinquenta e quatro qubits” - guarde esta informação - “durante 3 minutos e 20 segundos e a parte da supremacia é que o Google afirmou que levaria 100 mil computadores convencionais o equivalente a mil anos pra executar o mesmo cálculo, bem longe do que seria prático e portanto eles atingiram a definição de supremacia do Preskill.” - só enfatizando, veja que foi um cálculo de muitos minutos e não instantâneo. Muita gente pensa que computador quântico é instantâneo, mas não é. - “Mais especificamente o paper mostra que o experimento implementou um circuito com um depth ou profundidade 20, com 430 gates de 2 qubits e 1.113 gates de 1 qubit.“ - Essa profundidade é a quantidade de portas quânticas na maior sequência, que nem você viu no diagrama que mostrei antes pra teleportação ou hello world. Quatrocentos e trinta gates de 2 qubits, seria como uma porta CNOT pra entanglement por exemplo e mil cento e treze portas de 1 qubit seriam como o X de Pauli ou Hadamard ou Phase Shift e outras portas lógicas, todas de um qubit.

No segundo parágrafo da citação eu falei o seguinte: “Essa notícia causou furor na mídia e a IBM entrou no drama dizendo que o resultado é bom mas não é tão dramático assim. E que se usar o Oak Ridge Summit, o super computador da IBM que ocupa o espaço de 2 campos de basquete com seus 250 petabytes de espaço em disco, eles poderiam escrever os 9 quadrilhões de amplitudes em disco, quebrando a premissa de escrever o vetor inteiro em uma simulação de tipo Schroedinger pra uma simulação de Schroedinger-Feynman fazendo trade-off de espaço por tempo. E fazendo isso, em vez dos mil anos que o Google disse que ia levar, o IBM Summit poderia simular o mesmo cálculo em algo próximo de dois dias e meio, que é uma ordem de grandeza muito menor.”

Em resumo, eu falei que eles usaram cinquenta e três qubits nesse cálculo que é um espaço de 2 elevado a cinquenta e três e isso dá 9 quadrilhões de valores possíveis. Transformando isso em petabytes seria uns 8 petabytes, ou seja, poderíamos literalmente escrever um a um todos os valores possíveis em disco e colocar no storage de duzentos e cinquenta petabytes. Daí em vez de computar os valores podemos buscar o valor no disco. Eu estou simplificando bastante, claro. Isso é bem menos trivial do que parece só dizendo. Por exemplo, parece que o IBM Summit consegue escrever em disco na velocidade de 2.2 terabits por segundo de dados sequenciais, algo em torno de 275 gigabytes por segundo.

Pra escrever 8 petabytes, sem processar nada, só escrever sem parar, ia levar nada menos que 9 horas só pra escrever. Por isso eu imagino que o argumento da IBM seria que levaria 2 dias e meio em vez dos mil anos que o Google falou, é o tempo de computar e gravar todos os valores em disco, o jeito mais brute force possível. Mas isso é o tempo mais otimista possível usando o valor que é o pico de velocidade e você sabe que nenhum drive consegue sustentar o pico o tempo inteiro. Digamos que ele decaia pra na média metade da velocidade. Já aumenta o tempo pra quase um dia inteiro só escrevendo os dados todos! Em resumo o argumento da IBM tem alguma lógica mas até ser experimentado é só uma teoria. E considere que são 2.5 dias usando o supercomputador mais rápido do mundo em máxima capacidade só pra cuspir um número aleatório. Pense em quanto isso custa.

Se você seguiu com atenção até agora você tem uma intuição de como começa o que o Google fez. Mas temos o problema do erro que eu falei pra você guardar na cabeça, lembra? Cinquenta e quatro qubits parece bastante coisa, porém as chaves que usando hoje pra criptografia assimétrica com algoritmos como RSA tem tamanho de 2048 bits. Se eu não entendi errado, seria necessário no mínimo essa quantidade de qubits funcionando em entanglement ao mesmo tempo pra se fazer o cálculo, a menos que seja possível particionar o problema. Na minha falta de matemática, eu chutaria que não dá porque estamos procurando fatores primos desse comprimento. Pra particionar precisaria haver uma forma de fatorar esse primo, o que obviamente não é possível, portanto você precisa procurar o número inteiro de uma vez só. E isso é literalmente metade do problema.

A parte importante é quando eles falaram que 1 dos qubits não funcionou, então o cálculo foi feito com cinquenta e três qubits. Esse é o calcanhar de Aquiles de toda a computação quântica. Lembram que eu descrevi como um qubit fica totalmente isolado, mantido a quase zero absoluto, num ambiente o mais livre de interferência possível? Qualquer coisa pode excitar o núcleo e mudar o spin dele ou pior, tirar o núcleo do estado de superposição. Uma partícula em superposição colapsa no instante que alguma coisa observa ele; e o ambiente está constantemente tentando observar a partícula. Micro vibrações, campos eletromagnéticos, fótons, outras partículas subatômicas, qualquer coisa que chegar perto pode flipar esse núcleo. Então nesse momento o que se consegue construir é extremamente frágil. E eu disse que o núcleo consegue manter seu estado por mais tempo, mas esse tempo não é dias ou horas, pode ser minutos ou segundos. Quanto maior a profundidade de portas quânticas, maiores as chances de um dos qubits colapsar no meio do caminho e você tem que começar tudo de novo. Existe um efeito chamado “crosstalk” onde um qubit por causar interferência no outro também.

Decoherence ou decoerência é quando um qubit em superposição é colapsado nos bits clássicos antes do tempo por exemplo. Ou quanto o entanglement falha. E como eu disse qualquer coisa pode causar isso. O sistema precisaria estar 100% isolado pra garantir coerência permanente. Só que você precisa conseguir enviar microondas e ler os resultados, em algum momento você precisa causar decoherence pra ler o resultado, por exemplo e isso torna o sistema aberto com o ambiente. A maior quantidade qubits que já se colocou junto é no computador da D-Wave que já tem 2048 qubits mas ele não é igual os computadores da IBM ou do Google, a princípio ele não é universalmente programável, é hardcoded pra operações específicas e teoricamente não é capaz de rodar o algoritmo de Shor.

Sobre o mínimo necessário de qubits, na realidade é mais do que parece. Segundo algumas estimativas, com a implementação do algoritmo que se sabe fazer, seria necessário no mínimo 4000 qubits pra conseguir quebrar os algoritmos de chave pública que usam o conceito da dificuldade de fatoração de inteiros. Isso é o que teoricamente quebraria o mecanismo de troca de chaves que usamos hoje. Estamos falando especificamente de RSA e Diffie-Hellman. Troca de chaves é o que acontece toda vez que você conecta num site com certificado SSL ou TLS ou quando você se conecta via SSH em algum servidor. Esse sistema normalmente é usado pra autenticar a identidade dos participantes e depois pra trocar um segredo entre eles. Esse segredo é usado em algoritmos simétricos como RC4 e AES pra de fato realizar a encriptação da comunicação.

Pra entender esse conceito recomendo assistir os seguintes vídeos do Computerphile e da PBS Space Time que explica em mais detalhe. Talvez eu faça um vídeo resumindo esses conceitos depois. Mas entenda que RSA é um sistema de encriptação assimétrica cara. Por custar caro ela não é usada comumente pra encriptação dos dados em si. Ela é usada pra autenticar as partes e daí trocar uma chave simétrica, que tem tempo de duração curta, e que daí é usada por algoritmos como RC4 ou AES pra encriptar a comunicação num comunicador por exemplo.

AES, que é um algoritmo de encriptação simétrica, ou seja, uma função reversível, é que você usa pra encriptar e decriptar os dados da partição do seu disco por exemplo. É literalmente inquebrável. O algoritmo de Shor não vai quebrar ela. E ainda não tem um algoritmo conhecido que possa fazer isso. O problema que se encontra no AES é sempre na implementação, algum bug que expõe a chave simétrica que deveria ser secreta. Por exemplo, ficou sobrando na memória RAM e um malware consegue interceptar da memória. Ele não quebrou o AES, ele só roubou a chave que ficou na memória por engano. Por isso mesmo eu sempre falo: jamais tente implementar um algoritmo como RC4 ou AES você mesmo pra usar em algum projeto seu, você necessariamente vai implementar errado. Mesmo quem tem anos de experiência implementa errado, você que nunca fez isso vai com certeza absoluta implementar errado. Eu implementaria completamente errado. Faça como exercício mas jamais pra usar de verdade. Pra isso já existem bibliotecas abertas que já foram escrutinizadas por milhares de pessoas e tem maior garantia que funcionam como no clássico OpenSSL ou nos mais recentes como libsodium.

Só pra ninguém comentar que esqueci de mencionar. Algoritmos de encriptação simétrica como AES poderiam sofrer ataques usando o algoritmo quântico de Grover, que poderia ser usado pra aumentar muito a velocidade numa pesquisa num banco de dados não indexado por exemplo. Ou a definição que mais interessa que seria inverter uma função, por exemplo uma função de encriptação simétrica pra fazer ela decriptar. Porém Grover não é mágica, é um algoritmo que trás uma aumento quadrático em velocidade se comparado com o que temos hoje, mas mesmo isso não é suficiente se a chave for grande o suficiente. Hoje usamos AES-256 bits como o mais seguro, bastaria dobrar pra 512 ou até 1024 e nem um computador quântico conseguiria quebrar. Esse é o poder da exponenciação. E falando especificamente de criptomoedas ainda não há indícios que computação quântica poderia comprometer os algoritmos atuais e mesmo os mais paranóicos já tem altcoins que usam algoritmos teoricamente mais resistentes a uma eventual real supremacia quântica. O IOTA é um deles.

O campo de estudo de algoritmos quânticos ainda está no começo. Existem poucos algoritmos ainda. Com as características e potenciais de reduzir processamentos com complexidade exponencial em uma complexidade menor tipo logaritmica, isso abre a fantasia pra resolver diversos problemas que hoje custa muito caro ou é simplesmente inviável num computador tradicional. Por isso se fala em aplicação na indústria genética ou farmacêutica pra simular moléculas e drogas de forma melhor. Mas isso é como sonhar com as possibilidades de renderização 3D em tempo real em 4K quando o transistor nem foi inventado ainda. Estamos a décadas de distância disso.

A realidade parece ser mais a seguinte: todo o futuro da computação quântica depende de alguém descobrir como reduzir drasticamente o problema de noise e decoherence pra ser prático colocar milhares de qubits em superposição e entanglement e o sistema inteiro não colapsar antes do tempo. Não existe um jeito padrão eficiente de fabricação como temos CMOS e MOSFET pra transistores hoje em dia. Estamos realmente nos primeiros dias ainda. Por isso é um campo extremamente interessante e fascinante. Eu mesmo já tô viciado em estudar a respeito. Eu tive que me forçar a parar pra escrever o script de hoje porque tem centenas de coisas que eu ainda não entendi direito e queria conseguir verbalizar melhor. É como se eu estivesse em 1948 quando o transistor acabou de ser inventado. Ninguém tinha idéia de pra onde podia ir. Os transistors originais eram feitos do elemento germânio, só depois descobriram que silício era melhor. Qubits é a mesma coisa, eu mencionei átomos de fósforo, mas não é o único elemento viável. E nem a forma de fabricação com cristais de silício. Tem muito chão pela frente ainda.

Quando o Google anunciou a Supremacia Quântica, nós que nos inserimos dentro das ciências da computação ficamos otimistas na possibilidade de vermos um uso prático de computação quântica ainda dentro do nosso tempo de vida, ou seja, antes de morrermos. Por isso eu chuto que estamos a algumas décadas de distância de começarmos a usar em alguma escala útil em vez de só experimental e acadêmico como é hoje.

Se um computador quântico de 4000 qubits existisse hoje, poderíamos descobrir chaves privadas de algoritmos como RSA hoje. Mas como eu disse estamos longe disso. Mas até esse computador existir, o resto do mundo não está parado. Já existem diversos estudos de algoritmos pós-computação quântica. Algoritmos que não usam a dificuldade de fatoração de números primos ou problemas de logaritmos discretos ou problemas de logaritmos discretos de curva elíptica.

Como última tangente, nos últimos 5 anos atingimos um teto na Lei de Moore que você já deve ter ouvido falar, que inclusive não é uma lei mas uma observação, de que a cada 2 anos você consegue dobrar o poder computacional de um processador ao mesmo preço. Mas nós batemos no teto do Gigahertz, e desde então não aumentamos os clocks com a mesma velocidade de antes. Daí começamos a ter multi-cores, primeiro com o IBM POWER4 e depois com o AMD Athlon 64 e finalmente com o Intel Core 2 Duo. Na última década também os GPUs começaram a despontar e a diferença é a densidade de cores ou o que a NVIDIA chamaria de CUDA cores. Nesse período viemos quebrando programas feitos pra rodar num único core e tentando quebrar em trabalhos que podem ser divididos em múltiplos cores tanto na CPU ou se forem trabalhos envolvendo vetores, matrizes ou tensors, que é justamente o tipo de trabalho necessário pra processamento gráfico e machine learning, usamos GPUs.

Mas nem todo trabalho é paralelizável e existe outra lei que andamos esbarrando que é a Lei de Amdahl, na realidade é uma fórmula pra calcular o máximo de melhoria de performance que podemos ter pra determinados trabalhos paralelos. Na prática quer dizer que temos um limite pros nossos algoritmos de até onde dá pra melhorar a performance só aumentando cores na máquina e você já deve ter visto isso. Vou deixar linkado abaixo o video do canal Coreteks que explica em detalhes a evolução dos processos de fabricação de processadores e as mudanças em arquiteturas e pra onde ainda vamos com isso.

Um GPU ou unidade de processamento gráfico funciona em um SIMD ou Single Instruction Multiple Data ou SIMT que é Single Instruction Multiple Threads, que é uma versão um pouco modificada de um SIMD com funcionalidade de multithreading. Comparado com um CPU ele tem muito mais cores mas cada um é muito mais simples e é mais lento. O que a quantidade de cores permite é o aumento do throughput. Mesmo que uma tarefa individual demore mais comparado a um CPU. Um GPU é tão simples que nem tem um contador de programa. Muitas threads trabalham juntos num warp que no caso de uma Nvidia são grupos de 32 threads. Então todas as threads em warp precisam estar executando a mesma instrução ao mesmo tempo.

O que ajuda a performance de uma GPU é a memória mais rápida que ela tem. GPUs normalmente tem mais banda o que ajuda a alimentar a enorme quantidade de cores com dados o suficiente pra eles não ficarem sem nada pra fazer. O que torna um GPU especial é a capacidade de ter um grande número de tarefas simples e independentes rodando em paralelo. Tradicionalmente GPUs eram usadas só pra trabalhar gráficos, já que cálculo de vetores e matrizes é particularmente bom pra processar matrizes de pixels que compõe cada frame de uma animação por exemplo. Mas com a melhoria nos GPUs e algoritmos, finalmente conseguimos programar coisas mais sofisticadas com coisas com GPGPU. Treinamento de Machine Learning é a aplicação de funções de otimização em pontos de dados que é o tipo de coisa que GPUs fazem melhor.

Porém um GPU não substitui um CPU pela limitação na sua lógica de controle, pelo menos até agora, e depende do CPU pra organizar e delegar as tarefas e dados pra ele. Mesmo GPUs, que são desenvolvidos com tecnologias similares a CPUs em termos de transistors e materiais como silício, levou décadas pra chegar no ponto que estão hoje. E se você ver os vídeos do canal Coretek vai ver que ainda tem bastante coisa que já está em desenvolvimento em relação a otimização pra aumentar ainda mais a performance e baixar o uso de energia, em particular colocar o CPU e GPU no mesmo chip e mudar de arquitetura pra ARM ou RISC V por exemplo. Ainda temos décadas de evolução em cima do que já temos.

O computador quântico eu vejo mais sendo um QPU, e o que teríamos no futuro seria um híbrido de CPUs, GPUs e QPUs. O CPU continuaria sendo o coordenador. Veja os exemplos de simuladores como o Qiskit que mencionei antes pra ver que é exatamente assim que é montado hoje: um programa tradicional conecta numa QPU virtual e manda as operações, assim como fazemos com GPUs hoje. Trabalhos facilmente paralelizáveis continuariam sendo delegados pra coisas como as GPUs do futuro e trabalhos que possam tirar vantagem das propriedades de superposição e entanglement, como a transformada quântica de Fourier, poderiam ir pra essa QPU.

Não imagine um computador quântico como sendo o substituto do computador atual. Um computador quântico não deve rodar Linux e certamente não vai rodar Crisis. Assim como uma GPU é um hardware especializado que não substitui um CPU, um QPU seria a mesma coisa. Tecnicamente ela parece ser Turing Complete mas é o caso onde tarefas triviais seriam ainda executadas melhor e de forma mais rápidas por CPUs. Então a QPU seria mais como uma terceira placa - no dia que for possível diminuir o aparato que temos hoje pro formato de algo parecido com uma placa. Na prática por muitos anos ela vai ser mais como os super computadores, como os da Cray ou o IBM Summit, computadores hiper caros pra usos específicos em agências de governo ou grandes empresas, não vai ser algo acessível pra população em geral até que façamos o salto equivalente da válvula pro transistor. Na realidade eu diria que ainda não temos o equivalente ao transistor no mundo dos qubits.

Por último, quero repetir de novo. Os comportamentos quânticos como superposição, entanglement e coisas assim só fazem sentido no mundo subatômico. E segundo a interpretação de Copenhagen não faz muito sentido ficar especulando demais a natureza por trás disso, porque ainda nem não temos elementos suficientes pra discutir. Vamos direto aos cálculos e aos experimentos que de fato funcionam. Como diria o professor David Mermim “Shut up and calculate”.

Portanto, como eu já disse no começo, esqueça qualquer coisa pseudo pop científica que fica prometendo soluções quânticas pra sua vida. Mais energia. Mais conectividade. Mais empatia ou qualquer bobagem pseudo-quântica. Guarde isso pra ficção científica. Não existe isso no mundo macroscópico. Porém, os efeitos quânticos são reais e observáveis. Seu computador, os sensores da sua câmera digital, raios laser que você usa pra operar os olhos, basicamente todo uso avançado do efeito fotoelétrico é baseado em mecânica quântica. Toda essa teoria não é só acadêmica ou experimental, nós já nos beneficiamos da mecânica quântica em tudo que é microeletrônico e em particular que trabalha a luz de alguma forma.

No canal do MinutePhysics tem outro video explicando o efeito estranho usando filtros polarizadores pra mostrar um efeito quântico com a luz. E este vídeo do canal Domain Science explica os usos na vida real em mais detalhes e vou deixar os dois linkados abaixo também. O que você precisa levar de tudo isso que eu ruminei nesse vídeo é que mecânica quântica é algo real, matematicamente calculável, e usado na prática. Semicondutores obedecem as leis da física quântica por exemplo. E computadores quânticos realmente existem, podem ser usados hoje e simulados por você mesmo em casa. Ainda não sabemos se vai ser de fato prática de ser construída e usada mas isso é especulação de futuro, algo na ordem de décadas ainda, com sorte enquanto ainda estivermos vivos.

E é isso aí, nem vou perguntar se vocês tem dúvidas, porque hoje eu não consegui nem raspar o pico do iceberg. Milhares de pessoas estão aprendendo isso em cursos na faculdade ao redor do mundo hoje, e eu sou nada além de um mero iniciante nisso. Mas, fazer seu primeiro qubit realizar uma coisa considerada ficção científica como teletransporte é fascinante! De qualquer forma, se você tem mais informações científica pra compartilhar não deixem de mandar nos comentários abaixo, se curtiram o video deixem um joinha, compartilhem com seus amigos e não deixem de assinar ao canal e apertar o sininho pra não perder os próximos episódios. A gente se vê, até mais!


[Akitando] #65 - A Dor de Aprender | Que Cursos/Livros?

DESCRIPTION

Pra continuar esta sequência respondendo algumas perguntas frequentes que as pessoas me tem feito em mensagens diretas em redes sociais, no episódio de hoje que explorar as perguntas de "Quais cursos são melhores? Quais livros vou aprender mais rápido? Devo começar com Design Patterns?"

Eu não imaginava que as pessoas tinham tanta dificuldade no início, sobre o que exatamente devem fazer. Eu já falei bastante sobre "Aprendendo a aprender" e também sobre "prática deliberada" mas eu acho que nunca elaborei o suficiente pra ficar prático pra maioria das pessoas.

Então eu quero dizer mais claramente o que, na minha opinião, tem de errado na maioria das recomendações pra iniciantes que eu sempre ouço, e o que eu acho que é a melhor forma de pensar no aprendizado em geral.

Links:

  • Introduction to Algorithms (The MIT Press) 3rd Edition, Kindle Edition (https://www.amazon.com/Introduction-Algorithms-Press-Thomas-Cormen-ebook-dp-B007CNRCAO/dp/B007CNRCAO/ref=mt_kindle?_encoding=UTF8&me=&qid=)

SCRIPT

Olá pessoal, Fabio Akita

Ultimamente eu venho tentando responder algumas perguntas frequentes, por isso fiz os episódios de não terceirizar decisões e depois sobre os níveis de experiência. Mas tem outra pergunta que muitos iniciantes fazem bastante e eu tenho tentado responder direto de vez em quando mas acho que é hora de fazer um vídeo a respeito.

O episódio de hoje é pra explorar a seguinte pergunta: "Akita, que cursos eu deveria fazer ou que livros eu deveria ler pra começar em programação? Comprei o livro de Design Patterns, vai me ajudar?" Mas e se eu te dissesse que eu leio muito pouco livro técnico e acho que nunca fiz nenhum curso de programação de verdade até hoje?

Pra ser exato, se você assistiu meu vídeo sobre meus primeiros 5 anos, eu fiz um pequeno curso de programação sim, em 1990, quando tinha uns 13 anos, de Basic. Este episódio é um daqueles difíceis de organizar as idéias, porque dá pra explicar de dezenas de jeitos muito diferentes. Se você não assistiu os outros vídeos do meu canal, eu sou programador desde 1991 e de lá pra cá eu aprendi e trabalhei com dezenas de linguagens e frameworks diferentes, de comerciais como Java ou C#, a open source como PHP e Ruby, a mobile como Objective-C e entreguei projetos de todos os tipos. E desde 1990 eu venho aprendendo a maior parte do que eu sei quase sempre sozinho. A intenção do vídeo de hoje é ser meio repetitivo mesmo porque eu quero tentar marretar alguns conceitos na sua cabeça.

Quando eu entrei na faculdade eu já sabia o básico de programação. Eu não tinha a fundação pra explicar o porquê das coisas, mas eu conseguia achar as pistas iniciais pra começar a resolver um problema. Então a faculdade nunca me ensinou a “programar” propriamente dito. Agora, eu sinto que muita gente que ansiosa tá no problema anterior: você se sente devagar. Sente que estuda, estuda, mas não avança. Parece que todo novo problema te joga de volta à estaca zero. Você consegue resolver um problema, mas o próximo não consegue mais.

Parece que existe um consenso num caminho: faça um curso, compre um livro, daí tente fazer um projetinho seu do zero. Já ouvi muito isso. Mas eu acho que isso não é eficiente pro aprendizado. Pra que fique claro: eu não estou dizendo que o conteúdo em si seja ruim. Eu só acho que em alguns casos não se leva em consideração que existem dois tipos distintos de público.

Eu dividiria os iniciantes entre aqueles que naturalmente são autodidatas e os que não são. Os que não são, por diversos motivos, tem dificuldade de desviar do material que é apresentado. Elas se sentem mais confortáveis seguindo uma receita ou um procedimento. E quando algum passo diverge do procedimento eles ficam angustiados e travam e não conseguem saber que passo tomar em seguida.

Autodidatas naturalmente não se importam muito com procedimento. A gente obviamente tenta seguir, mas quando um passo não funciona, em vez de sentir angústia a gente sente uma raiva que nos leva a querer entender porque o passo falhou. Foi culpa do procedimento? Foi minha própria culpa porque errei algum passo anterior? Se for esse o caso, deixa eu voltar passos e tentar de novo. Deixa eu voltar pro começo e tentar tudo de novo. Ainda não funciona? Deixa eu pesquisar. Deixa eu perguntar pra outras pessoas. E assim vamos até uma hora avançar pro próximo passo. Por isso eu digo que autodidatas sempre vão avançar independente da qualidade do material de ensino. Porque a gente naturalmente não se conforma. E quando um autodidata se dá bem, não foi por causa de algum curso ou material, foi só a determinação de avançar mesmo.

Por isso eu enfatizei a idéia de não delegar suas decisões num vídeo inteiro. No aprendizado os autodidatas naturalmente tomam uma decisão: se o procedimento não funciona, vou tentar descobrir porque. O procedimento não decide por mim, eu decido pelo procedimento. Os não-autodidatas quando encontram uma barreira, eles travam, e tem a expectativa que alguém ou alguma coisa vai tirar eles do problema. Como assim o procedimento não funciona? Devia funcionar. Vou cruzar os braços e esperar até alguém consertar o procedimento. E vou reclamar que o procedimento não funciona. E vou querer meu dinheiro de volta. Em qualquer outro contexto isso não está totalmente errado, mas em aprendizado é o exato oposto de aprender.

Eu não sei como chamar um não-autodidata então me perdoem se o termo não soa bem, mas vou chamar de pessoas passivas, que esperam as coisas acontecer em vez de fazer as coisas acontecerem. Em um ambiente de aprendizado, você precisa ser diferente: você não quer que as pessoas digam o que você tem que fazer. Você quer referências e orientações mas não ordens nem procedimentos. Mas a maioria dos tutoriais e cursos introdutórios que eu vejo se preocupam em ensinar o "o que" em vez do "porque" e faz sentido. Explicar “o que” fazer é muito mais fácil do que explicar “por que”. Vejam este canal. O "o que" normalmente tá no título, ou no primeiro minuto do vídeo. É o porque que custa 30 minutos ou 1 hora.

Agora, se alguém quer ensinar, ele quer que os alunos tenham o máximo de satisfação possível no final. Se tentar ensinar de verdade, forçar os princípios, explicar os porquês, criar situações de frustração pros alunos. A maioria vai desistir. Porém, se focar mais em exemplos simples e procedimentos, até o pior dos alunos vai conseguir passar por todos os passos. E a satisfação no final vai ser maior porque fica a falsa sensação que aprendeu alguma coisa, quando na verdade só seguiu um procedimento. E qualquer um consegue seguir procedimentos, não requer talento, tampouco habilidade.

É que nem quando você era criança e tinha aqueles livros de colorir com espaços numerados bonitinho. No número 1 você pinta de vermelho, no 2 você pinta de verde, vai seguindo as instruções e no final você tem um desenho colorido bonito o suficiente. Você aprendeu a pintar? Não. Você aprendeu a teoria das cores e como harmonizar essas cores no desenho? Não. Você pintou um desenho mas não avançou nenhum passo em aprender arte. Isso se chama passatempo.

Eu não sei se vocês já se interessaram em assistir vídeos ou documentários sobre o início dos computadores caseiros no fim dos anos 70 e começo dos 80. Na Europa com os ZX-Spectrum. Nos Estados Unidos com os Commodore. Mesmo no Brasil com os TK que eram clones dos Sinclair. E vários outros modelos similares e simples de 8-bits ou 16-bits. Você podia programar em Basic ou Assembler, linguagem de máquina mesmo. Eram computadores super fracos, com pouquíssima memória e processamento, mesmo pros padrões da época. E eles costumavam vir com um manual de instruções que normalmente ensinava o básico de Basic .. alguns exemplos de códigos pra animações e joguinhos simples nível jogo da velha. E era só isso que você tinha.

Muitas vezes você sequer tinha onde gravar o que programava, porque gravador de fita cassette era opcional, disk drives de 5 e 1/4 ou 3 e 1/2 eram mais caros. HD então era inacessível, muitos desses computadores nem aceitavam. A maioria das pessoas se contentava em comprar joguinhos e programas educacionais e ser só um mero usuário do computador. Uma pequena parcela aprendia a programar nessas máquinas limitadas. Sem cursos. Sem livros. Sem internet pra pesquisar nem nada.

Vocês perceberam que eu já contei muitas dessas histórias em episódios anteriores? Eu posso ter sido sutil, mas eu falei de tudo isso até hoje pra ver se vocês chegavam na pergunta mais óbvia. Como pessoas como eu, quando criança, conseguimos aprender a programar num ambiente muito mais hostil do que hoje em dia?

Eu já dei metade da resposta no vídeo de não delegar suas decisões e também no de como eu aprendi inglês sozinho. E a resposta não é porque eu ou essas outras crianças da época éramos gênios, com QI superior ou qualquer bobagem dessas. A resposta mais simples é porque a gente não deixou de ser criança até hoje. Vou relembrar o que eu disse no vídeo de inglês. Muita gente tentando aprender a falar em inglês não consegue, porque tem vergonha de falar errado. Então fica nervoso, gagueja, dá branco, e isso deixa ainda mais nervoso, gaguejando mais. Até o ponto onde a pessoa conclui: ahhh isso Não é pra mim, eu não sou inteligente o suficiente pra aprender (chora).

Eu não sou um profissional de ensino, nem estudioso do comportamento humano, mas eu tenho uma teoria. Tirando os casos de real deficiência física, doença, ou algo assim, eu realmente não acredito que as pessoas sejam incapazes de aprender qualquer coisa. E eu não digo isso porque eu sou algum tipo de humanista e tenho fé nas pessoas ou qualquer bobagem assim. Na verdade é até o oposto. Mas deixa eu apresentar um fato pra vocês. No exemplo do inglês por exemplo, pra você que acha difícil falar, que acha que o curso é ruim, ou que o material que você tem é ruim, ou que você só é burro mesmo e nunca vai aprender. Você fala português?

Concorda comigo que 100% das pessoas nascidas no Brasil, com ou sem educação formal, todas falam português? Porque existiria uma deficiência pra se aprender inglês mas não existe uma deficiência que te tornou incapaz de aprender português quando você era criança? Minha teoria é que todos nós nascemos sem noção de "sentir vergonha" ou qualquer tipo de "auto-julgamento". Quanto mais nova uma criança, menos vergonha ela tem, mesmo falando errado. E ela fala errado o tempo todo, alguns adultos acham bonitos, outros acham irritante. A gente tenta corrigir e elas não ficam mudas de vergonha, elas atentamente copiam os adultos. Elas vão de poucas sílabas sem sentido, palavras que não formam frases direito, até conseguir argumentar coisas complicadas com os adultos. E tudo isso sem NENHUM treinamento formal.

Nenhuma criança precisou de curso pra aprender a chutar bola na rua. Nenhuma criança precisou de curso pra aprender a se comunicar com as outras no jardim da infância. Elas simplesmente vão e fazem. À medida que se envelhece - e aqui eu não tenho bagagem técnica pra explicar porque - cria-se uma barreira. Em algum momento você tentou fazer alguma coisa que ainda não sabia como, você errou, alguém te criticou ou tirou sarro de você, e você parou de tentar. A partir desse dia, tudo que antes você não julgava difícil começou a ficar angustiante, frustrante, você se sente desajustado, inadequado, burro e até mesmo incapaz.

A grande maioria das pessoas de 40 anos, se eu falar, suba num palco e fale. Ou, grave vídeos e mostre pras pessoas. Elas vão ficar aterrorizadas. Eu também fico. Até hoje quando vou dar uma palestra, não importa se é pra 500 pessoas ou pra 10 pessoas, me dá frio na barriga, começa a vir os pensamentos de "e se me der um branco". Se eu errar será que as pessoas vão achar que eu sou uma fraude? A diferença é que independente disso eu sou kamikaze, então eu ligo o "foda-se" e simplesmente vou. Pessoas como eu não fazem as coisas porque não temos medo. A gente faz as coisas apesar do medo. O medo que você sente eu também sinto e ela continua aqui. Mesmo depois de anos e anos. Você pode se esconder e evitar o medo ou você pode aprender a conviver com o medo. Em qualquer dos dois casos o medo vai continuar, então porque não só ligar o foda-se?

Deixa eu fazer uma tangente. Alguns meses atrás eu decidi comprar um uniciclo. É super difícil se equilibrar nessa porcaria. A dificuldade inicial é mais ou menos a mesma de skate, do básico de skate, quero dizer. Eu passei 2 ou 3 dias caindo no chão. Se você quer aprender coisas novas, você não pode pensar "puts, que coisa feia eu quarentão caindo no chão e me ralando todo como se fosse uma criança de 10 anos". É extremamente vergonhoso você sair na rua pra treinar e cair na frente dos outros. E eu tenho tomado tombo na rua mesmo. E o que eu vou fazer? Desistir e inventar justificativas de porque alguém da minha idade não devia estar fazendo essas coisas? Nem a pau.

E veja que interessante. A loja onde comprei até ofereceu um grupo de suporte pra iniciantes treinarem juntos. Mas eu sou muito mais teimoso e arrogante pra isso. Eu gosto de aprender as coisas sozinho. É uma coisa minha. Eu prefiro tentar usar meu tempo da forma mais eficiente possível e me deslocar pra algum lugar, num horário pré-determinado, nunca me pareceu eficiente pra mim. Eu não gosto de ter outras pessoas do lado pra cair na armadilha de usar a muleta do "puts, isso é difícil mesmo né? Se a gente tivesse 10 anos já tinha conseguido" e aí fica um se sentindo mais confortável porque o outro tá lamentando também. Eu vira e mexe me vejo lamentado mas eu odeio ficar me lamentando, e eu detesto mais ainda ouvir o lamento dos outros.

Então, pra aprender, eu comecei num sábado de manhã cedo e eu treinei sem parar, sem pausas mesmo, tomando tombo e tentando de novo, tombo atrás de tombo, até lá pelas 9 horas da noite que foi quando eu consegui andar em linha reta pela primeira vez sem cair. Eu terminei o dia com as pernas roxas, inchadas e raladas de tanto cair. E quando eu caía eu caía mesmo, deixava meu corpo inteiro ir pro chão. Acordei domingo cedo e fiz a mesma coisa, horas a fio até conseguir dar curvas. Eu tinha colocado na minha cabeça que eu ia aprender a andar naquela porcaria em 2 dias, então eu fui e fiz. E isso pra aprender o básico, não estou pulando rampas nem fazendo malabarismo não.

Como qualquer um faria, eu assisti videos de tutorial na sexta feira. Nenhum me ajudou muito porque nenhum disse a coisa mais óbvia. Você já tentou subir numa bicicleta e ficar parado em cima dela? Não dá, ela despenca pros lados. Como você faz pra não despencar? Você sobe na bicicleta já dando a primeira pedalada e sai pedalando. É a inércia do movimento que impede você de cair. É a mesma coisa no uniciclo, você precisa subir nela já andando. Sem isso você vai cair. Sei lá porque; tanto eu, como todo mundo que pediu pra tentar, o primeiro instinto é ficar de pé parado em cima do negócio. Agora que eu sei, é claramente idiota isso, porque ninguém vai conseguir ficar imóvel em cima disso, é impossível. No máximo você vai fazer a roda ficar girando pra frente e pra trás pra se equilibrar no mesmo lugar. Mas parado, parado é impossível.

E eu aprendi a andar a primeira linha reta quando esse insight me ocorreu. Daí o próximo passo foi brigar com o próprio corpo que instintivamente quer parar quando ele se vê indo pra frente sem as pernas estarem se mexendo. Sério, a memória muscular do corpo é uma droga. O corpo quer frear sozinho, daí são horas até você obrigar o seu corpo a desaprender e aceitar que o novo estado natural dele é andando pra frente em cima do negócio. Aprendizado, no seu núcleo, é uma briga de você contra você mesmo. Por isso eu acho que nós anti-sociais temos uma vantagem, porque a gente tá acostumado a brigar com nós mesmos.

Desculpa fazer essa longa tangente de uniciclo mas esse episódio me lembrou dessa situação, porque é igualzinho com qualquer outra coisa que você tá aprendendo. Primeiro, você precisa perder a vergonha de cair, porque é lógico, você é iniciante, você tem que cair, tem que se ralar todo, tem que ficar roxo e machucado. Segundo, você precisa desaprender o que você acha que sabe, porque senão você freia automaticamente e cai sem saber porque.

Respondendo a pergunta que eu fiz agora a pouco. Como nós aprendemos a programar naqueles computadores dos anos 80? A gente caía. Nossos códigos davam pau. A diferença é que a maioria desiste nas primeiras tentativas. Mas alguns de nós ficam roxos e não se importam de ficar mais roxos. Eu pensei nesse exemplo porque este fim de semana mesmo, lá estava eu andando de uniciclo na rua, não prestei atenção, tropecei e caí com tudo no chão. O que eu fiz no dia seguinte? Tomei um remédio pra dor e resolvi andar 20 quilômetros pela cidade, porque depois da queda eu senti meu corpo arregando e ficando com medo, então eu fui ensinar uma lição pra ele, que quem manda sou eu.

O prazer de um autodidata é provar pra ele mesmo que ele suporta a dor. A gente sente dor, igual todo mundo, mas em vez de evitar a dor, a gente faz esforço pra se acostumar a sentir dor. É bem doloroso quando você faz um curso, lê um livro, e seu código ainda não funciona ou você nem sabe como fazer um código diferente do que foi ensinado. É super frustrante. E sua primeira reação é o que? O curso é que não deve ter sido bom. Ou deve ter outro livro que vai ensinar de verdade o que precisa saber. Desculpe se eu sou o primeiro a dizer isso: a menos que você esteja disposto a encarar a dor, engolir o choro, esquecer a frustração, e começar a cair e ficar roxo na frente dos outros, não adianta nenhum curso, nenhum livro, nada, você nunca vai aprender. Ou você pára de desperdiçar dinheiro indo atrás de fórmula mágica, ou engole a dor, pára de mimimi, se compromete e foca no objetivo.

Vou repetir: programação é uma profissão de prática. Qualquer atividade manual como culinária, artes, esportes, como subir num uniciclo, tudo é prática. Eu aprendi o básico, sei andar num uniciclo, cobrir longas distâncias, e não cair, mas eu ainda não sei o avançado. Eu não sei pular rampas por exemplo, estou treinando agora a andar numa perna só. E porque eu faço isso? Não tenho a mínima idéia, eu simplesmente quero.

Até hoje todo mundo chama os invasores digitais russos ou chineses de hackers. O termo acabou sendo distorcido e hacker passou a significar, ou algo ruim e criminoso, ou algo tecnicamente muito difícil que exige um QI superior, como você vê num Mr. Robot da vida. Mas isso é o que antigamente a gente chamava de Cracker. Só que a definição original de hacker é só uma pessoa curiosa. Na verdade mais que só curioso, alguém que é curioso é alguém que faz a pergunta. Mas um hacker é alguém que também procura a resposta. Todo bom programador é um hacker.

Eu sempre achei que a forma mais eficiente de aprender é primeiro tentando fazer, de qualquer jeito, sem objetivos. Sem instrução nenhuma. Existem algumas etapas que você provavelmente vai passar. A primeira é a do zero. Não estou dizendo pra não fazer curso. Faça. Se você ainda não sabe, use pra aprender o básico do ambiente. Como eu uso o editor de textos que o curso apresentou? Como eu salvo o código? Como eu faço pra esse código rodar. Por que vocês acham que os vídeos técnicos que eu fiz primeiro são de como instalar o sistema operacional e configurar as formas de rodar as primeiras linguagens?

Mas você precisa também passar por um período, alguns dias por exemplo, de copiar código de livro, de tutorial ou de qualquer lugar do GitHub, sem se importar se você tá fazendo certo ou errado. Você tá nos primeiros dias. Sério, não tem importância nenhuma saber se você tá fazendo direito ou não. Vou repetir: não importa. A vantagem de código é que ele não custa nada. Você não gasta nada. Se der erro você não perde nada. Não se machuca. Eu gosto de correr antes de aprender a andar. Se cair do uniciclo não machucasse, a primeira coisa que eu ia fazer era pular de uma rampa, depois aprender a andar em linha reta. Mas eu provavelmente ia quebrar uma perna, então não dá pra fazer isso. Mas com código dá.

Corre e depois anda. Copia um monte de código. Parece idiota isso, mas abre um código do GitHub de um lado, o editor do outro lado e vai copiando, da mesma forma como você copiaria um texto de um livro no Word. A intenção é ganhar memória muscular. Ao fazer isso várias vezes, você vai instintivamente começar a ganhar noção de como os códigos são escritos, como programadores melhores que você dão nome pras coisas, com que estética. A silhueta do que é um código vai começar a se formar na sua cabeça. Nesse ponto não importa se esse código vai servir pra alguma coisa ou não. Só digita muito, por horas, dias. Você precisa se familiarizar com o máximo possível de código num período curto de tempo.

Eu aprendi a datilografar desse jeito. Lembram no episódio de Henry Jones que eu disse que eu copiava enciclopédia? Não era uma vez só não. Tinha verbetes que eu copiava múltiplas vezes, porque eu achava que não tinha ficado bom da primeira vez. Mas pra que fazer isso? Não tem nenhum motivo, só porque eu gostava da sensação de sair do ponto onde eu era devagar até ficar rápido copiando. Foda-se o conteúdo do que eu tava copiando. Eu tô dizendo pra você fazer a mesma coisa, mas não só pra ficar rápido. Copiando muitos textos eu pouco a pouco fui entendendo o que é o tamanho adequado de um parágrafo? Como é a composição das frases? Que palavras são mais ou menos usadas?

Pouco a pouco, treinando desse jeito, você vai percebendo uma coisa interessante: padrões. No sentido de repetições. Diferentes códigos de diferentes pessoas tem certos padrões embutidos nelas. Algumas são estruturais, algumas são estéticas. Pouco a pouco você vai diferenciando quais são obrigatórias, quais são opções estéticas. Nomenclatura das coisas. Tamanho das funções. Complexidade do código. Diferentes formas de resolver o mesmo problema.

Quando a gente é iniciante dificilmente pensa "quero fazer um programa X" e daí vai magicamente conseguir. Você não vai, e mesmo se chegar perto ou conseguir, vai estar uma droga. É o contrário. Você vai copiando muitos códigos, entendendo o que cada trecho faz. Aos poucos você aprende ... ahh é assim que se abre um arquivo. ahh é assim que se grava um arquivo ... ahh é assim que eu posso passar uma URL e baixar o conteúdo. Uma hora você pensa ... peraí, se eu consigo abrir e salvar coisas num arquivo e se eu consigo baixar coisas da internet ... isso é um programa que podia ser útil, vou fazer um programa que baixa os artigos dos sites que eu gosto e guarda em arquivos.

O objetivo aparece sozinho depois que você começa a aprender o que cada trecho diferente de código faz. Inverte sua expectativa. No fundo não importa o que você acha que quer fazer porque na prática você ainda não sabe o que é possível fazer, então suas idéias sempre vão ser medíocres e provavelmente tudo que você consegue imaginar já existe pronto. A menos que você já seja um estudioso de outra área, seja de física, matemática, direito, qualquer outra coisa, suas idéias iniciais sempre vão ser simplórias. Por isso Não importa tanto o objetivo do código, só importa você codar.

Estou sendo bem repetitivo porque parece que não é tão óbvio. Quantas horas você já gastou digitando código? A diferença entre hoje e na minha época é que hoje você tem um troço chamado GitHub onde pode baixar todo tipo de exemplo de código possível e imaginável. Eu nunca mais ia dormir se aos 13 anos eu tivesse um GitHub. Falando em não dormir, o que eu vou dizer agora é o clichê do clichê mas eu vou dizer mesmo assim. Pessoas como eu amam fazer as coisas a noite. Estudar ou qualquer coisa. E existe uma grande razão pra isso: todo mundo tá dormindo de noite. Ninguém te liga. Ninguém te manda mensagem. Ninguém te atrapalha nem te interrompe. Se você mora com outras pessoas na mesma casa, elas provavelmente estão dormindo.

A melhor coisa pra estudar ou produzir coisas criativas é estar completamente focado. É o estado que as pessoas olham pra mim e me chamam de autistas. Porque quando eu quero entrar nesse modo eu literalmente ignoro todo mundo. Meu celular fica permanentemente em “Não perturbe”. Eu literalmente não atendo meu celular. Mesmo mensagens em whats ou direct ou email, eu raramente respondo na hora. Eu respondo quando eu estiver com vontade de responder, nunca antes. E por isso também eu gravo e edito meus vídeos a noite. Não tem barulho, não tem maldito cachorro barulhento do vizinho, não tem barulho de carro, nada pra atrapalhar. Você precisa encontrar seu espaço isolado e usar ele pra estudar e produzir. E não existe mais viciante e mais improdutivo do que redes sociais. Redes sociais é a definição de perda de tempo. Não tem absolutamente nada em nenhuma rede social que exige sua atenção imediata. E se você se acostumar a não querer engajar em alguma coisa na hora que você vê, e espera 5 minutos, quando voltar vai ver que não era assim tão importante que merece seu tempo de comentar. Threads de redes sociais são basicamente coisas inúteis, mas é bom pra quando você vai no banheiro e já tá enjoado de candy crush.

A idéia não é você sair contribuindo em projetos de código aberto também. Se tiver a oportunidade pode fazer, mas não é esse o objetivo principal pra você que é iniciante. O objetivo é você pegar fluência no código. Fluência não é fazer tudo certo em todos os passos. É você dar a cara pra bater e errar sem se intimidar em sentir vergonha. Fluência em uma língua nova, tipo inglês, é a mesma coisa, é a atitude de simplesmente falar, mesmo se estiver errado, e não ter vergonha e se intimidar quando alguém te corrigir.

Você vai notar que a recomendação aqui é igual do vídeo de como aprender inglês. Porque é a mesma coisa. Em vez de tentar decorar gramática, decorar vocabulário e tentar escrever um texto todo certo da primeira vez, e se frustrar, porque obviamente você não vai conseguir. Eu acho mais fácil ler e ouvir o máximo possível de tudo que é lugar. Igual quando você aprendeu português quando era criança. Você aprende a falar igual o que se fala na televisão e igual os adultos ao redor falam. Você não aprendeu nenhuma regra gramatical, você aprendeu via reconhecimento de padrões, ou patterns.

Nós seres humanos somos muito bem equipados pra detectar patterns via nossos sentidos. O que significa isso? Significa que somos bons em detectar coisas que se repetem ao nosso redor. Mas é uma faca de dois gumes. Ilusões de ótica, por exemplo, brincam com os bugs nesse nosso sistema. Muitas vezes esse sentido nos engana, e aí criamos superstições. Por exemplo, associamos muito tempo atrás a correlação de que passar embaixo de uma escada dá azar. Provavelmente porque algumas pessoas que foram vistas passando debaixo de uma escada depois sofreram algum infortúnio. E o pattern de "passar embaixo de escada e sofrer" acabou pegando. A maioria das besteiras que dão trending no Twitter são isso: falácias lógicas por viés de confirmação, uma falha de correlação, vítima do nosso excelente sistema de detecção de patterns. O principal: só porque você nota uma grande repetição de alguma coisa, não a torna boa, pode ser o que chamamos de anti-pattern.

Eu já expliquei no meu vídeo de inglês sobre a diferença entre patterns e padrões. Mas vou repetir aqui de novo pra relembrar. Em inglês existem três palavras diferentes que traduzem pra padrão em português. Temos Standard que é "o" padrão, como um regulamento ou regra a ser seguida. Temos Default que é a escolha padrão, ou seja, dentro de várias opções se você não escolher nenhuma o sistema pode escolher o default. Default pode ser também quando você não paga uma dívida e dá default nela. Finalmente, o que a gente usa em tecnologia que é "um" padrão ou repetição, isso é um Pattern. Patterns não representam uma regra ou regulamento ou o melhor jeito, são simplesmente coisas que se repetem várias vezes.

Isso é importante explicar aqui porque várias vezes muitos iniciantes me falam que estão estudando o livro de Design Patterns do Gang of Four, e me perguntam se isso vai ajudar eles a virarem programadores melhores. Minha resposta é simples: se você é iniciante, isso vai ajudar muito pouco pra quase nada. Patterns é uma coisa que você vai entendendo melhor ao longo do tempo e não adianta só estudar uma vez, você vai revisitando de anos em anos e vai entendendo melhor a cada nova revisitada.

Entendam, a idéia de patterns foi inspirada no livro A Language Pattern do arquiteto Christopher Alexander. Arquiteto de construções de verdade mesmo. Ele compila os patterns vistos em arquitetura que podem ajudar na composição de novos designs usando uma linguagem comum usada a séculos em construções pelo mundo. É um catálogo de organização urbana com mais de 250 patterns que descrevem a visão do Alexander de como uma cidade perfeita poderia ser. Do layout das ruas, até o café da esquina.

O que é um pattern em arquitetura. Vejamos um problema trivial: como entrar e sair de uma casa? Um buraco com uma tábua que abre e fecha. Nome do pattern? Porta. Dá pra implementar de outras formas? Claro, pode ter uma câmera e um computador pra abrir e fechar sozinha. Pode ser redonda em vez de retangular. Pode ser uma porta com outra porta no meio.

Agora, você não precisou ter o livro do Alexander pra saber o conceito da porta. Se você construir uma casinha do zero e esquecer da porta, você não vai conseguir entrar na casa. Mas você sempre pode pegar uma marreta e abrir uma porta na força pela parede. É meio idiota mas funciona. Em código é mais fácil. Se você esquecer da porta, sempre pode refatorar ou reescrever o trecho do código que precisava da porta. O custo do código é proporcionalmente quase zero se comparado a corrigir um erro no mundo físico, de tijolo e madeira.

Meu ponto é que os patterns surgem da observação de centenas de construções. E criam uma linguagem pra descrever os elementos dessas construções. Milhares de pessoas ao longo da história vieram implementando essas construções e agora só ganharam um nome pra podermos falar delas. Eu pensei isso quando o livro do Gang of Four saiu pela primeira vez em 1994. Lembre-se que antes disso a gente já fazia bons softwares e não havia uma formalização de patterns. Ela é útil e importante, mas não é crucial, especialmente no começo.

O livro ganhou notoriedade porque coincidiu com o nascimento do mercado de Java. Então pra uma novo mercado de programadores, orientação a objetos e design patterns pareciam uma coisa nova que andavam juntos e eram a “regra”, o “standard” a ser seguido. Mas na realidade, comece a programar, observar o código de outras pessoas, e você naturalmente chega na maioria dos patterns do livro. De command, a visitor, a observer, a builder. A grande maioria dos patterns, aliás, foi derivado de frameworks de interfaces gráficas como Motif nos Unix. Se você já teve a oportunidade de criar ou usar uma toolkit de elementos gráficos como um tcl/tk ou Qt ou Gtk ou WPF ou Apple UIKit, vai facilmente ver muitos dos patterns do gang of four neles.

E esses não são os únicos patterns. Hoje em dia, por alguma razão, o Domain Driven Design do Eric Evans, que foi lançado em 2003 foi ressuscitado por causa do famigerado pattern Repository. No mundo Ruby on Rails a gente se inspirou no Patterns of Enterprise Application Architecture do Martin Fowler pra pegar emprestado o pattern que ele cunhou como ActiveRecord. Todo guideline visual de interfaces gráficas da Apple, do Gnome, e outros são patterns de como implementar aplicações gráficas coerentes.

Tudo isso e muito mais são patterns. Mas eu acho muito pouco útil estudar os patterns sem ter experiência anterior em programação. Eu acho mais útil estudar patterns depois que você já treinou escrevendo muito código e lendo muito código dos outros. Daí o estudo dos patterns vai ajudar a criar uma linguagem pra descrever patterns de programação que você já estava acostumado a ver mas só não sabia os nomes. Foi o que aconteceu comigo. Ah, isso que eu fazia se chama Visitor, bom saber. Ah, esse outro jeito aqui é um chain of responsability, que fancy, bom saber também.

Se você com pouca experiência começa estudando e decorando os patterns primeiro e depois vai querer tentar aplicar, você vai usar tudo errado. Patterns são ferramentas. Você não deve usar todas ao mesmo tempo. Cada problema tem uma ferramenta pra escolher e nem sempre essa escolha é simples. Até hoje muita gente discute ActiveRecord versus Repository. Eu acho engraçado povo discutindo isso em 2019 sendo que foi ponto de grande discussão em 2003 ao ponto de eu ficar enjoado de ouvir sobre Repository e na época era versus DAO ou Data Access Objects que a Microsoft usava bastante.

Vejam, 16 anos depois e até agora as pessoas ainda não chegaram num consenso sobre acesso e abstração de dados. Além de ActiveRecord, Repository, DAO ainda temos outros patterns como Data Mapper, Table e Row Data Gateway e outros patterns. Patterns não são regras, não são “o” jeito certo de resolver todos os problemas, são apenas repetições, cada qual emergiu pra resolver um problema diferente numa situação diferente.

Por isso eu digo que tanto faz que curso você faça, que livro você leia, no fundo eles são quase todos iguais porque todos vão ensinar mais ou menos do mesmo jeito: do jeito linear, passo a passo, que raramente é o que você vai encontrar no mundo real. No mundo real você tem um problema pra resolver, e sua solução vai depender do quanto você treinou com quantas ferramentas e técnicas diferentes. Pra chutar uma combinação de elementos que pode ou não resolver o problema da forma mais eficiente possível, dentro das limitações e circunstâncias em que você se encontra. Quanto menos você treinou, menos opções você vai ter. Se você só treinou um jeito, você sempre só vai querer resolver de um jeito, e normalmente não é o jeito mais eficiente. E por isso eu falo pra não se apaixonar por uma linguagem ou uma ferramenta. Daí vem o ditado: pra quem só conhece martelo, todos os problemas parecem pregos.

Alguns anos atrás eu comecei a estudar Elixir, Crystal e Rust. Eu li a documentação dos sites deles primeiro. Eu procurei os poucos tutoriais que eles tinham disponíveis 4 anos atrás. E eu simplesmente fui codando, qualquer coisa. Códigos sem nenhum propósito além de exercício. A minha vantagem quando aprendi essas linguagens é que eu já tinha experiência de programação em outras linguagens, então eu não levei mais que 30 dias pra aprender cada uma. A que eu até hoje tenho menos fluência é Rust porque foi o que eu menos treinei. Elixir foi o que tinha melhor material pelo fato que ele pôde aproveitar as décadas de existência e maturidade do Erlang que está por baixo dele, então o framework OTP já era bem compreendido e com inúmeros exemplos, patterns e anti-patterns conhecidos e bem documentados. Crystal foi quase trivial porque ele se valeu de usar quase a mesma sintaxe de Ruby e usar os patterns de Go pra concorrência, então se você já sabe Ruby ou se você já sabe Go, pular pra Crystal é uma questão de poucos dias.

Quanto mais você vai desistindo a cada dificuldade que aparece e desiste de ir até o fim pra resolver, mais difícil as coisas vão ficando. Se você aprender a aprender, que é hackeando seu caminho, resolvendo problemas que o material não previu, ou seja, aprendendo a ser autodidata, a vantagem é que quanto mais você aprender assim e criar seu próprio ritmo, menos difícil vai ficando aprender coisas novas. A maioria das coisas novas é uma melhoria de coisas anteriores. Então as primeiras linguagens que eu aprendi em 1990, que foram Basic e dBase, são hiper simples pros padrões de hoje, mas eu levei meses e meses pra aprender. Porque eu não tinha nem material, nem base de referência. Linguagens ordens de grandeza mais complicados como um Elixir, pra mim, é uma questão de semanas pra me tornar produtivo nelas. Não me tornar um expert, cuidado, me tornar produtivo.

A idéia não é falar mal dos cursos e livros, só apontar que é impossível criar um curso ou livro que consiga te mostrar como é programação na vida real. Eles virariam livros de milhares de páginas e extremamente tediosos de ler. A estrutura de cursos e livros é tentar transmitir um conhecimento no menor tempo e esforço possíveis. É como um livro de artesanato, ele pode te dar passo a passo de como pregar madeira e prego e fazer uma cadeira. Mas se você nunca usou um martelo nem nunca cortou madeira na vida, na realidade você vai quebrar um monte de tábuas, entortar um monte de pregos, provavelmente vai martelar o próprio dedo e se cortar um monte de vezes até conseguir fazer uma cadeira que não vai se parecer nada com a foto do livro. No final a diferença é só isso: quem desiste, o caminho acaba aí. Não tem outro caminho a não ser ir em frente, independente da sua frustração ou da sua vergonha.

Sobre conhecimento de fundação. Eu diria que se tem uma matéria de faculdade que é obrigatório e separa os amadores e hobistas dos que vão fazer uma carreira de verdade em programação é Algoritmos e Estruturas de Dados. Qual é a diferença entre um Tuple ou uma Lista Ligada? Qual a diferença de um String de conteúdo imutável e um String Buffer que você pode adicionar e modificar o conteúdo? Aliás, se é tudo binário, qual é a diferença de um integer e um string? O que de fato é a diferença entre um float e um double? Toda linguagem tem essas coisas, é a base de TODAS as linguagens. Um Java parece difícil porque ela tem umas 20 classes diferentes só pra Listas. Por exemplo, qual a diferença de um ArrayList, um LinkedList, um Stack, um SortedSet, uma Queue?

Daí você precisa aprender: qual a forma mais fácil de estruturar os dados pra ser fácil recuperar o que você precisa depois? Vai passar pelas coisas triviais como Quicksorts ou Mergesorts até chegar em coisas mais legais como Bloom filters e até hash rings e consistent hashing. Quando você entende esses elementos, coisas como computação distribuída, replicação de bancos de dados como NoSQL e tudo mais começam a clicar na sua cabeça mais fácil. Mas pra chegar nisso você precisa sair do básico. Entender as estruturas de dados e algoritmos mais simples primeiro, de um mero struct de C, union types, nomes como markov, levenshtein, huffman e assim por diante.

Algoritmos e Estruturas de Dados eu não vejo como você poderia aprender organicamente, sem um material formal. Vai precisar estudar a teoria e praticar centenas de vezes pra realmente ganhar fluência. Por isso eu acho que linguagens como C ou Pascal eram boas pra começar, porque você era obrigado a entender essas coisas pra fazer qualquer coisa. A maioria das linguagens modernas esconde essas coisas de você, então você não nota que vai precisar até muito tarde.

Esquece estudar Design Patterns ou Arquiteturas de Sistemas antes de aprender Estruturas de Dados e Algoritmos. Estou repetindo isso várias vezes pra vocês colocarem isso na cabeça. Antes de dominar o básico, esquece o avançado, não vai acontecer. É que nem querer aprender culinária e achar que não precisa aprender Mis en place ou descascar batata. Tudo que você constrói precisa de fundações sólidas. Quanto mais porcaria for sua fundação pior vai ser seu puxadinho. Você vai bater num teto muito rápido e não vai conseguir crescer, e seu puxadinho toda hora arrisca desabar. Se preocupe em fundações sólidas, não em hype da moda.

Hype da moda, linguagem ou framework da moda, tudo isso é simples. Sério, se você tem boas fundações, aprender um novo framework “tem” que ser simples. Aprender uma nova linguagem tem que ser simples. Claro, ganhar fluência exige tempo, mas você não vai ficar coçando a cabeça toda hora ficando frustrado porque não consegue começar a aprender. Você tem dificuldade de aprender porque tem preguiça e tá postergando sofrer a fundação, que é chato, teórico, difícil e parece que não tem utilidade. Mesma coisa técnica de usar a faca direito pra cortar as coisas. Você teima em não aprender, e não consegue cortar mais rápido sem cortar os dedos juntos. Porque você é burro, a gente já sabe o jeito certo, isso é o básico, a fundação, o que todo mundo que vai cozinhar já devia saber.

Aliás, caso não seja óbvio, frameworks são implementações de patterns pra resolver um determinado tipo de problema. Por exemplo, frameworks de GUI pra interfaces gráfica como o UIKit da Apple ou frameworks web como Rails e Laravel pra aplicativos web. Ou frameworks como OTP do Erlang pra sistemas distribuídos. Frameworks são manifestações em forma de implementação de patterns reusáveis de programação.

Outra coisa interessante é que muitos design patterns podem ser considerados defeitos nas linguagens. A gente costuma chamar esse tipo de boilerplates, que são trechos de código que todo mundo meio que convencionou a fazer toda vez. É uma sensação de .. “porque eu estou tendo toda hora que fazer esse mesmo código em todo lugar em vez da linguagem ou mesmo do framework fazerem isso por mim?” .. É quando você é obrigado a criar funções ou classes vazias ou arquivos pra lá e pra cá, só pro negócio compilar ou rodar.

Eu vou dizer que ainda não tô totalmente contente com essa minha explicação até agora. Mas os pontos principais pra resumir: Primeiro, estude obrigatoriamente Algoritmos e Estruturas de Dados. Segundo, desligue redes sociais, coloque seu celular em modo de não perturbe o tempo todo. Terceiro, não se preocupe em codar coisas com objetivo no começo, os objetivos do exercício não importam, o único objetivo é codar e codar. Quarto, não importa qual curso e qual livro você vai escolher pra aprender a primeira linguagem, tanto faz mesmo, a diferença entre a maioria dos cursos mais conhecidos é marginal. Quinto, treine, treine, treine e treinar não é fazer projetinho do zero. Nada de bom sai de uma cabeça vazia. Treinar é copiar qualquer código sem nenhum objetivo, quebrar esse código, combinar códigos de pessoas diferentes, mas principalmente, copiar a maior quantidade de código dos outros quanto possível. Só depois de copiar e copiar trechos por centenas de horas você vai começar a identificar sozinho alguns patterns. Quando finalmente você se sentir confortável com os puxadinhos de código que começam a funcionar de forma precária, talvez você possa estudar os diversos design patterns que existem por aí só pra dar nome pros seus puxadinhos. E é assim que você começa.

Eu falei isso no vídeo de Conhecimentos Gerais no começo da série Começando aos 40. Eu acabei de dizer que a coisa mais importante é se expôr à maior quantidade de código dos outros quanto possível, mesmo se não entender nada da primeira vez que ler. É como aprender uma nova língua e desligar as legendas dos filmes. A idéia é você sofrer mesmo, e aprender a lidar com essa angústia de não estar entendendo nada. Pra isso você pode e deve usar o GitHub que está dando sopa com milhões de projetos em todas as linguagens e frameworks que você pode imaginar. É a biblioteca de Alexandria do código. Pra isso, a primeira coisa que você deve aprender é a usar Git, e o Google tá aí pra isso. Hoje em dia tem muito tutorial simples. Se você ainda não sabe, aprende. Qual é a desculpa?

E é isso aí, eu queria deixar uma resposta mais completa pra essa indagação do que eu estava conseguindo dizer nas mensagens diretas. Espero que tenha ajudado a criar um modelo mental do que você devia estar fazendo pra superar essa fase inicial de aprendizado em qualuqer coisa. Se ficou com dúvidas não deixe de mandar nos comentários, se tiver outras dicas também são muito bem vindas. Se curtiram o vídeo mandem um joinha, não deixem de assinar o canal e cliquem no sininho pra não perder os próximos episódios. A gente se vê, até mais.


[Akitando] #64 - Começando na Carreira de TI | Faculdade? Níveis de Experiência?

DESCRIPTION

Continuando na vibe de iniciantes na carreira de TI eu sei que ainda tem muita dúvida sobre os diferentes cursos da faculdade como Ciências da Computação, Engenharia de Software ou Sistemas de Informação.

Além disso todo mundo começando tem dúvidas sobre os diversos níveis de experiência como Júnior, Pleno ou Sênior.

Como iniciante, como eu deveria pensar sobre esses assuntos? Hoje eu resolvi compilar uma longa explicação sobre o que eu costumo ver ao longo de uma carreira, desde a faculdade até a evolução de júnior até sênior.

Links:

  • AkitaOnRails [Off-Topic] Opiniões, Verdades, Democracia e Ética (https://www.akitaonrails.com/2011/05/04/off-topic-opinioes-verdades-democracia-e-etica)

Akitando:

  • Dúvida: Devo Fazer Faculdade? (https://www.youtube.com/watch?v=XWVcF7BoCSc)
  • Devo Fazer Faculdade? (22 Anos Depois) (https://www.youtube.com/watch?v=iRjEa7N8wEo)
  • O que Eu Devo Estudar? (https://www.youtube.com/watch?v=Ll1uAZ-WRa0)
  • O Mercado de TI para Iniciantes em Programação (https://www.youtube.com/watch?v=O76ZfAIEukE)
  • Você não sabe nada de Enterprise! Conhecendo a SAP (https://www.youtube.com/watch?v=FXhcfJnlD2k)
  • Não Terceirize suas Decisões (https://www.youtube.com/watch?v=D3L8IOncLkg)

SCRIPT

Olá pessoal, Fabio Akita

Eu percebi pelos comentários que muita gente assistindo meu canal é iniciante de alguma forma em computação, seja porque está na faixa dos 20 anos realmente começando do zero ou porque está na faixa perto dos 40 querendo dar cento e oitenta graus e mudar de carreira completamente.

Então no episódio de hoje eu quero explorar um pouco mais sobre a temática da evolução na carreira. Por exemplo, muita gente ainda tem dúvidas sobre a famigerada tríade que todo RH fala mas quase nenhum sabe definir, o que é júnior, pleno ou sênior. Também um pouco sobre as diferenças nos diversos cursos de faculdade, tipo engenharia de software ou engenharia da computação. No final um pouco sobre o contexto onde os dois pontos anteriores. Pra variar é tema que eu deveria quebrar em uns 3 episódios separados mas queria tirar isso do caminho de uma vez só, então preparem-se que vai ser bem longo!

Como eu disse no episódio anterior sobre Não Terceirize suas Decisões a idéia aqui não é dar nenhum passo a passo pra vocês. Não quero que ninguém pense que eu estou recomendando fazer de um jeito ou de outro. O que eu vou contar hoje vai ser muito mais minha opinião do que uma definição formal, então se você já é experiente e já está avançado na carreira, provavelmente vai discordar de mim em muitos pontos e nesse caso não deixem de compartilhar sua visão nos comentários abaixo. Carreira não é uma receita de bolo certinha, o máximo que podemos fazer é dar perspectiva.

Vamos começar do começo. Eu vou dizer que até eu fico meio confuso com os diferentes cursos em faculdades. Existem faculdades que usam o mesmo nome de curso mas ensinam coisas diferentes, acho que principalmente em Sistemas de Informação. Existem faculdades que ensinam de formas diferentes. Fora os diferentes níveis de qualidade e reconhecimento. Então dependendo de onde você estudar, vai ter diferenças consideráveis. Infelizmente eu não posso ajudar muito aqui, não existe nenhuma forma objetiva de comparar curso a curso de todos os lugares.

Quando eu comecei a escolher cursos pra faculdade lá por 94 haviam principalmente 3 cursos diferentes. Engenharia da Computação, Ciências da Computação e Processamento de Dados. Até onde eu entendia, Engenharia da Computação é de fato uma engenharia, na realidade eu chutaria que é uma especialização de Engenharia Elétrica. Esse é o curso praqueles interessados no hardware em si, microprocessadores, placas lógicas e você teoricamente deve aprender a desenhar circuitos, desenhar placas, propriedade dos materiais e até processos de fabricação.

Normalmente os diversos cursos de engenharia todos começam igual, você tem dois anos da mesma grade e no meio você escolhe pra qual engenharia vai se especializar, tipo civil, naval, produção, elétrica, mecânica, etc. Pelo menos na USP era assim. Os primeiros anos é muita matemática, e ainda assim menos do que nos cursos de matemática mesmo, mas é cálculo, álgebra, física. Confunde um pouco porque você tem programação no currículo, mas eu imagino que é mais voltado à programação de mais baixo nível. Porta lógicas, circuitos integrados, semicondutores, talvez se fale em coisas como CMOS e MOSFET, VLSI, etc. Se você quer trabalhar na indústria de eletrônicos e automação por exemplo, seria esse o curso.

Ciência da Computação foi o que eu fiz, e eu diria que é uma especialização da Matemática Aplicada. A matemática pura, como você pode imaginar, não se preocupa tanto com a aplicação. Já computação é uma aplicação da matemática, por isso eu digo que seria uma especialização. Os dois primeiros anos de Ciências da Computação é muito parecido com os cursos de matemática mesmo e por isso muita gente não gosta porque se vê muito pouco de programação nesse período. Só a partir do 5o período você começa a ver mais coisas como montadores, compiladores, sistemas operacionais.

O objetivo de um curso de ciências não é ensinar o que o mercado do momento precisa. Você até tem matérias opcionais, que tentam introduzir alguma coisa mais prática, mas não é esse o foco. Computação, como eu disse, é uma aplicação da matemática, toda a base de conhecimento que torna um computador possível vem de décadas atrás, começando com a arquitetura de Von Neumann. Não importa se o computador de hoje é ordens de grandeza mais rápido que um dos anos 80, fundamentalmente eles ainda operam da mesma maneira. Mesma coisas como linguagens de programação. Tanto faz qual é a linguagem mais moderna de hoje, um Java de hoje fundamentalmente funciona da mesma forma que um Basic nos anos 70, só é mais complicado. Em vez de aprender só uma linguagem, você deveria aprender como todas elas funcionam no nível dos compiladores, por exemplo.

O que na minha época era Processamento de Dados ou mesmo Análise de Sistemas hoje em dia acho que é chamado de Sistemas de Informação. O grosso da computação aplicada na indústria continua sendo muito de “processamento de dados”. Por exemplo, numa multinacional de 10 mil funcionários, todos eles batendo cartão de ponto, imagine contabilizar as horas trabalhadas de todos, na mão. Ou imagine fazer o fechamento contábil de uma rede de varejo no papel. Ou no Brasil que você tem uma tonelada de regulamentações pra seguir, Reinf, SPED, ECF e por aí vai.

Eu não acompanhei a transição dos cursos de Processamento de Dados pra Sistemas de Informação mas o objetivo é parecido, que é a automatização de processos envolvendo informação na forma de sistemas, por isso Sistemas de Informação. Teoricamente você se forma tecnólogo ou bacharel e pode atuar como um analista de automatização de processos de informação. No mundo corporativo que é formado de diversas áreas como recursos humanos, vendas, financeiro, manufatura, etc todas precisam ser integradas, automatizadas, e um analista de sistemas de informação seria o profissional trabalhando nisso. E já que mencionei tecnólogo e bacharel vale esclarecer. Eu entendo que ambos são nível superior, e um bacharel é mais generalista do que um tecnólogo, e cursos de tecnólogo são mais curtos e mais focados.

E isso me leva pra outro curso que me parece que começou a aparecer mais nos anos 90 que é Engenharia de Software. Pelo menos pra mim esse sempre foi mais estranho. O maior problema de software, do ponto de vista das metodologias de programação, é que eu nunca consegui enxergar tanto como uma "engenharia" da mesma forma como engenharia civil ou mecânica por exemplo. Acho que de todas as engenharias, a de software é a única que não lida com hardware ...

Software em si não obedece leis da física. Quantidade de código não aumenta seu “peso”. Ele não tem cheiro, não tem textura, nada, é totalmente abstrato. Poderíamos escrever software pra uma máquina infinita teórica, com poder de processamento infinito, armazenamento infinito e velocidade de transferência de dados infinitos. Seria como na matemática pura. No mundo real, pra não dizer que não existe nenhuma lei, software é de fato limitado pelos ciclos do processador, pela capacidade de armazenamento e pela velocidade de transferência dos dados entre os diversos componentes.

Mas do ponto de vista do código em si, ele não tem limites. Eu posso literalmente escrever qualquer coisa. Tecnicamente, as linguagens também não tem limites, sendo todas Turing Complete eu posso escrever qualquer software em qualquer linguagem. Eu gosto da definição de engenheiro como sendo aquele que aplica os princípios da ciência e da matemática pra desenvolver soluções economicamente viáveis pra problemas técnicos. É o elo que liga as descobertas científicas às aplicações comerciais.

A parte da ciência que o engenheiro usa vem da ciência da computação cuja função é resolver problemas computacionais ou melhorar a performance das soluções existentes, é realmente mais voltado a uma forma de pensar mais de pesquisa, não necessariamente que a pessoa precise virar um pesquisador. Seja novos algoritmos. Seja novos componentes de computação como novas linguagens ou novos sistemas operacionais ou novos protocolos de rede. Veja os produtos que um Google lança como Protobufs, a virtual machine Dalvik, os algoritmos de pesquisa, bibliotecas de gerenciamento de memória melhores, tudo trabalho de pesquisa de ciências da computação. Daí você tem ferramentas como a linguagem Go, o framework Flutter, um Kubernetes, tudo trabalho de engenharia. E finalmente a aplicação dessas tecnologias pra criar produtos como o Google Analytics, o Gmail, o Google Maps, que poderia sair da mão de analistas de sistema e engenheiros trabalhando juntos.

Porém, os papéis não são tãaaao bem divididos assim. Existem cientistas com cabeça de produto, existem engenheiros com cabeça de pesquisa, existem analistas com cabeça de engenharia. Pode ter gente que saíram de diferentes cursos convergindo pros mesmos papéis. A beleza do mundo de tecnologia é que me parece mais fácil um cientista da computação ir pra engenharia de software do que um arquiteto de verdade virar engenheiro civil. Posso estar falando uma grande bobagem, mas é como a analogia se encaixa na minha cabeça.

De todos os cursos, o mais fácil de escolher talvez seja engenharia da computação, porque você já tem na cabeça que quer lidar com hardware, entrar na indústria de automação, produção, eletrônicos e coisas assim. Talvez a dúvida seja se você quer engenharia elétrica com especialização em computação ou engenharia da computação propriamente dita. Mas eu não estudei engenharia o suficiente pra saber qual o melhor.

Se seu foco for programação, você pode virar um programador mais cientista, ou mais engenheiro, ou mais analista. A grosso, bem grosso modo, é como eu dividiria se quisesse simplificar beeeem simplificado. Generalizando eu diria que a maioria dos autodidatas que já começou trabalhando por conta e aprendendo sob demanda, acaba sendo mais um programador analista, porque falta a fundação formal de ciências e engenharia. Elas podem ser aprendidas sozinho, mas a maioria dos autodidatas jovem sequer sabem da existência dessa fundação.

Como eu sou de ciências da computação, eu sempre vou ter um viés pra ciências. Mas não existe necessariamente uma correlação entre o curso que você escolher e seu sucesso no futuro. O sucesso depende mais do tipo de pessoa que você é, do que o curso em si. Todos os cursos são só bases pra começar. Eu sempre vou defender que você aprenda matérias que historicamente sobreviveram ao teste do tempo. Uma matéria como, digamos, Flash que tenha aparecido na grade de um curso no começo dos anos 2000, hoje é inútil por exemplo. Quanto mais específico em alto nível, mais rápido tende a ficar defasado, mas mais prático no dia 1. Quanto mais específico em baixo nível, mais tende a ter valor ao longo do tempo mas menos prático vai ser no dia 1, como cálculo ou estatística.

Como eu também sou defensor de jogar o jogo do longo prazo, eu defendo que um curso de programação cujas matérias tendem a te acompanhar por mais tempo no futuro seja ciências da computação e depois as engenharias, e um bacharelado antes de um tecnólogo. Mas isso é um compromisso de longo prazo, ou seja, você realmente não tem intenções de mudar de área. Mas eu sei que muita gente não tem essa certeza aos 17 anos. Por isso eu não sou contra cursos como sistemas de informação e tecnólogos. Você sempre pode estudar ciências depois. Vai ser bem mais difícil, especialmente se você estiver trabalhando, mas não é impossível dado que existem centenas de casos de pessoas acima dos 30 ou 40 anos fazendo exatamente isso.

Suas opções ficam mais limitadas, claro, é sempre um trade-off. Não existe uma decisão correta com resultado garantido, você vai ter que jogar. Suas chances sempre vão ser fifty-fifty, cara ou coroa. Alguém decidido a não mudar de caminho, como eu, que decidi por ciência como primeira opção e me segurei nela desde então, é só um cara teimoso. Por sorte eu dei certo. Nem todo mundo precisa ser teimoso, e eu conheço muita gente mais bem sucedida que eu que mudou de caminho várias vezes. Tudo depende de que tipo de pessoa você é.

Um médico tem como prioridade salvar o paciente que está na sua frente na melhor das suas habilidades e usando os recursos que existem hoje. Um pesquisador de laboratório tem como prioridade salvar o máximo de pacientes no futuro, aprimorando ou inventando tratamentos e ferramentas que vão equipar os hospitais e consultórios no futuro. Nenhum dos dois está errado e um precisa do outro pra continuar progredindo.

Um cientista da computação pode ter como prioridade a pesquisa de como aprimorar o gerenciador de memória de uma linguagem pra que ele use menos recursos e performe melhor no mesmo hardware. O correto é ele usar cálculo e estatística nesse processo. O engenheiro de software pode ter como prioridade os diferentes processos e técnicas de como usar as linguagens pra desenvolver aplicações com o melhor custo benefício. Ele pode usar princípios da engenharia de produção. Um analista de sistemas tem como prioridade usar essas linguagens e essas técnicas pra automatizar o problema real de uma empresa hoje, assim como um médico. Porém, diferente de um médico que tem um CRM e não tem autorização pra inventar um novo tratamento e testar em pacientes, um analista pode sim dar uma de engenheiro ou cientista e testar alguma coisa experimental, embora não seja essa sua prioridade.

Alguém com formação em ciências ou engenharia teria mais capacidade de desenvolver o software que equipa o sistema de estacionamento automático de um carro, mas provavelmente não um analista. Por outro lado, alguém com formação em ciência não tenha paciência pra analisar e resolver os problemas de dia a dia de uma empresa como um analista. E um analista talvez não tenha paciência pra ficar inventando soluções como um engenheiro.

Eu posso ficar o dia inteiro fazendo comparações e não vamos ter raspado a ponta do iceberg. É importante você, que é iniciante, entender que independente do que você escolheu e estudou, ao final da faculdade, você ainda não está nem perto de estar pronto. Programação, seja com perfil mais de cientista, ou mais de engenheiro, ou mais de analista, continua sendo uma profissão de prática. Agora são mais longos anos de prática na área.

E aqui começa outro problema pra mim. Júnior, pleno, sênior. O único mais ou menos fácil de definir é estagiário porque a CLT define isso. Existe um TCE ou termo de compromisso de estágio assinado entre a empresa contratante, o estudante que é o estagiário e a instituição de ensino. Se você não sabia disso, pesquise a respeito. De qualquer forma é uma atividade remunerada mas não de carteira assinada, com carga horária menor do que o normal, supervisionada, com tempo máximo de 2 anos.

Eu diria que a nova lei prejudicou a vida dos autodidatas porque não existe mais a oportunidade de estágio fora de vínculo com uma instituição de ensino. Independente se a instituição tem estágio obrigatório ou não, se possível faça, a partir dos 2 últimos anos do curso. A maioria faz no último ano. E eu sei que em cursos de federais de ciências da computação pode ser difícil porque o curso começa difícil e vai ficando mais difícil até o trabalho de conclusão de curso e muitos tem período integral. A intenção dos cursos de bacharelado em ciências da computação me parece ser mais formar pesquisadores e não tanto inserir no mercado de trabalho.

Se você for recém-formado ou mesmo recém-iniciado a trabalhar na área, tendo tido educação formal ou sendo autodidata, você é um júnior. Basicamente quer dizer que você tem pouca ou nenhuma experiência observada na área. De repente você é um gênio prodígio com QI 500 ou sei lá, mas ainda não há resultados observáveis. Não existe um tempo fixo pra você deixar de ser júnior. As primeiras experiências vão te ajudar a decidir melhor que área você prefere seguir. Se você é mais analista, mais engenheiro ou mais cientista e o que vai continuar estudando sozinho na sequência.

O analista e o engenheiro costumam ter mais opções, especialmente no começo. Um cientista puro que é bom em pesquisa mas não tem paciência pra resolver os problemas "mundanos" vai ter mais dificuldade e em 2019 tende a ir pra áreas como data science, machine learning ou algo assim em tech startups, ou a tentar entrar em empresas realmente grandes que tem condições de ter área de pesquisa e desenvolvimento, como os Google ou Microsoft da vida. A maioria das empresas médias e pequenas não tem o porte necessário pra suportar pesquisa ainda.

Eu por exemplo, apesar de ter estudado ciências tenho mais perfil de engenharia e principalmente de analista, eu tenho zero paciência pra escrever papers, fazer experimentos elaborados demais, e apesar do meu foco ser bom por longos períodos, eu logo mudo de foco de novo. Por alguma razão meu interesse acabou se voltando em resolver problemas mais "reais" e menos abstratos, parte disso porque eu fui consultor corporativo por muitos anos.

Mas eu estou me adiantando. No geral, os níveis de experiência em programação, pra mim, funciona assim: o mesmo problema dado pra um júnior, um pleno e um sênior, podem ser resolvidos com qualidade similar. O que vai variar é o tempo. O júnior não tem experiência, até achar a melhor combinação de programação e arquitetura que resolve o problema da forma mais eficiente, vai levar muitas tentativas e muitos erros. O pleno, por já ter tido mais experiência, vai ter uma gama menor de tentativas e erros, e um sênior provavelmente já viu problema semelhante e já vai ter meio caminho andado pra resolver o problema.

Intuitivamente qualquer não-programador poderia assumir que o código de um júnior vai ser pior do que de um pleno, que por sua vez vai ser pior que de um sênior. E também que o melhor código sempre vai ser do sênior. Eu não gosto muito dessa definição, ela só quer dizer que alguém deixou o júnior subir código em produção sem ninguém avaliar antes. E isso é verdade na maioria das empresas. Todo mundo só vê artigo de blog, papers, palestras de médias e grandes empresas, o 1% do mercado. Mas os 99% que não tem tempo nem recursos pra produzir esse material é justamente porque também não tem muito recurso pra supervisionar os funcionários mais inexperientes e por isso a qualidade geral do código tende a ser baixa.

Eu fico repetindo aqui sobre a dificuldade de definir porque é muito mais fácil encontrar definições que vem de empresas grandes, que podem se dar ao luxo de dar todo o suporte possível aos júniors. Mas essa não é a realidade. A realidade são pequenas empresas, com no máximo 5 ou 6 desenvolvedores, ou menos, entuxados de trabalho até o pescoço, vendendo o almoço pra comprar a janta. Esquece a pompa, esquece metodologia, esquece boas práticas. Especialmente quando os donos também são júniors ainda. Então é um navio sem capitão, à mercê da próxima onda.

A grande maioria das microempresas, infelizmente, não vai sobreviver. Dependendo de qual estatística você procurar, 4 em cada 5 devem fechar antes de completar 5 anos. Não é fácil ser empreendedor, especialmente no Brasil, onde você tem um sócio obrigatório, que não trabalha, só atrapalha, e leva 20% do seu faturamento independente se você teve lucro ou não, e ele deixa o prejuízo pros sócios de verdade, que trabalham. Nem vou entrar nesse mérito hoje.

Como se tudo isso já não fosse obstáculo suficiente, muitas grandes consultorias e fábricas de software tem mania de vender júnior como se fosse pleno, pleno como se fosse sênior e sênior como se fosse expert. É uma estratégia arriscada que funciona quando você é do tamanho de uma Accenture da vida. Por outro lado, num mercado como dos Estados Unidos, o que a gente aqui chama de sênior, pra eles ainda é mid-level, o que a gente chama aqui de pleno ainda é júnior e o nosso júnior seria um trainee talvez. A educação formal nos Estados Unidos, é no geral melhor mesmo.

Considerando um programador júnior com formação em faculdades dos Estados Unidos, nativo e cidadão americano, o salário varia na faixa de uns 40 mil dólares ao ano. Pense em algo na faixa de uns 12 mil reais por mes aqui. Considerando impostos, seria o salário de um pleno, talvez indo pra sênior aqui em São Paulo. Agora, na Accenture da Índia, um programador sênior ganharia na faixa de uns 600 mil rúpias, que é a moeda de lá. Uma rupee é 6 centavos no Brasil, então ele faz o equivalente a menos de 3 mil reais de salário, que seria faixa de júnior.

De novo, esses valores são só exemplos, porque salários varia de lugar pra lugar. Mas tem programadores na Índia que ganham tanto quanto um programador nos Estados Unidos, são raros mas tem. Mesma coisa no Brasil. Tudo depende do que você produz. Se você é o raro tipo de pessoa que tem capacidade e experiência pra produzir código de deep learning e redes neurais que ninguém mais consegue, eu nem sei porque você tá aqui vendo meu canal. Mas se você faz o mesmo tipo de código que todo mundo faz, obviamente seu salário sempre vai ser mais baixo. Sempre vai ter alguém na Índia que faz melhor e mais barato que você, se lembre disso.

Por outro lado, Índia é exatamente outro lugar que júnior é vendido como sênior. Deve ter um sênior lá a cada esquina, literalmente. Por isso a reputação é baixa e o valor é proporcionalmente baixo também. No caso geral, valor não é algo que você determina o que você acha que merece. Valor é determinado por o que essa entidade chamada "mercado" determina. Ache isso justo ou não. É sempre uma negociação. Se você não estiver disposto a negociar, no geral, vai ganhar o que vale, e costuma ser menos do que você pensa. Quanto mais cedo você aceitar isso, menos vai gastar de psicólogo.

Eu levantei esse ponto da remuneração porque na prática, o que todo mundo que discute sobre junior, pleno e sênior quer saber é quando vai ganhar mais, ou porque o cara do lado que parece que sabe menos que você ganha mais que você. E de novo eu vou repetir o que eu já disse em vídeos anteriores. Tirando os casos óbvios de abuso na empresa ou pura negligência mesmo, se você for júnior, a menos que você realmente esteja ganhando tão pouco que mal dá pra sobreviver, não é algo que você deveria se preocupar, ou pelo menos não é o que deveria estar no topo da sua lista de prioridades. A prioridade pra um júnior é estar perto de pessoas mais experientes e num lugar com desafios. Onde você tem alguém que critique o que você está fazendo, aponte o que você está fazendo errado.

Eu estou falando tanto de júnior porque infelizmente saltar de júnior pra pleno e de pleno pra sênior não é uma coisa objetiva. Não é que nem num videogame que de repente você tá com novecentos e noventa e nove pontos de XP e com mais um ponto tcharam deu level up. Não tem isso. Durante sua carreira você vai gradativamente subindo de nível de conhecimento, nível de responsabilidade, qualidade, reputação e vários outros pontos. No final do dia, quanto mais seus colegas e seus superiores vão te dando mais e mais confiança, e quanto mais você consegue corresponder entregando com mais e mais qualidade, mais você continua subindo. Quanto mais você cortar passos, tiver entregas irregulares, quebrar confiança, menos você avança. Ninguém dá muito a mínima quando você as coisas certas, mas basta uma coisa errada e todo mundo nota. E é assim mesmo que as coisas funcionam. Se fosse fácil, qualquer um fazia.

E confiança também funciona assim: quebre uma vez e conseguir de volta custa bem caro. Quando você é claramente júnior, tanto de idade quanto de tempo de experiência, erros são mais toleráveis porque as expectativas não são tão altas assim e erros são esperados. É uma fase onde a medição é se você pelo menos é esperto pra aprender com os erros e não errar na mesma coisa toda vez. É meio óbvio, mas vale dizer.

À medida que o tempo passa, você passa por mais desafios, e vai chegando na tal fase mid-level ou pleno, que apesar de não existir um tempo pré-definido, numa carreira normal, costuma ser depois dos 3 anos de casa e vai durar mais alguns anos. Aqui você corre o perigo de cair na armadilha do que chamamos de Dunning-Kruger effect. É um viés cognitivo onde as pessoas têm a incapacidade de auto avaliar as próprias inabilidades, em resumo, elas se acham melhores do que realmente são. E na minha experiência, eu já vi vários júniors de potencial caindo nisso e regredindo, o que é um desperdício.

Eu nunca parei pra pesquisar tanto desse efeito. Esse nome acho que foi cunhado só por volta de 2011 que foi quando todo mundo ficou blogando a respeito. Mas eu mesmo passei por algo assim na fase que eu era júnior também. Felizmente eu tomei vários tapas na cara e voltei pra realidade, mas quando a gente é novo, dinâmico, estuda sem parar e dá aquela impressão que você é melhor que todo mundo ao redor porque todo desafio que cai no seu colo parece que você consegue resolver, é extremamente satisfatório. E a tendência é você sentar em berço explêndido e estagnar, ou pior, regredir. Você precisa fazer um esforço consciente de imaginar que você sabe menos do que realmente sabe sem se tornar um vitimista de Síndrome do Impostor, quando na verdade você é só um impostor mesmo.

E esse é o truque. Na realidade você é as duas coisas: muito bom em poucas coisas, mas muito ruim em várias outras coisas. Só que isso é normal. Você dificilmente vai automaticamente bom em tudo só porque subiu de nível em uma coisa. Quando se é júnior é fácil porque você é júnior em tudo. Mas quando você está indo pra pleno ou mid-level, você é pleno em algumas coisas mas não em tudo. E quando novos desafios aparecem que você ainda não teve oportunidade de enfrentar, você fica confuso porque “como assim você que é pleno e experiente tá tendo dificuldade de júnior de novo?” É uma transição que pode ser meio desconfortável pra alguns. E muita gente começa a naturalmente tender pra lei econômica do menor esforço: entre tarefas que você já sabe fazer muito bem e tarefas desafiadoras que você não sabe fazer ainda, você começa a escolher só as que sabe fazer.

E é uma das razões de porque eu sempre odiei ser elogiado, especialmente por quem não é tecnicamente melhor que eu. É um incentivo muito forte pra te manter na zona de conforto. Por que você vai sair dessa zona e se arriscar a fazer alguma coisa onde você ainda é júnior, correndo o risco de falhar e as pessoas que te elogiavam depois te olharem com aquele olhar de "nossa, eu esperava mais de você", fazendo você se sentir um impostor? Aí quando você fala que não é tão bom assim, as pessoas ainda vem com o olhar de "nossa, olha o falso modesto".

Tem gente que naturalmente não liga muito pra isso, eu era o tipo que ligava e às vezes ainda caio na bobagem de ligar, então tive que aprender a desligar isso e ignorar sinais externos desnecessários. A única avaliação que interessa é sua auto-avaliação. Avaliação externa que tem importância é a crítica, porque ela pode indicar um defeito na sua auto-avaliação. Mas elogios não significam nada e ainda confundem sua auto-avaliação, e não dão dica nenhuma da direção certa ou errada.

Como eu disse no vídeo sobre não terceirize suas decisões, este é um caso clássico. Você está terceirizando a avaliação que você mesmo devia fazer pra outra pessoa que não sabe 100% dos detalhes de tudo que você faz. Só você sabe tudo que você faz todo dia, toda hora. Se você considera que faz "muita coisa" mas parece que ninguém dá bola, você devia avaliar se o que você faz tem de fato o valor que você acha que tem. Talvez você faça coisas demais que tem pouco valor. Aliás, você só pode produzir ou não produzir alguma coisa. O valor quem determina é quem paga, você pode pedir mais, mas de novo, é uma negociação. Digamos que você resolveu fazer mil origamis, é um puta esforço e dá um puta trabalho. Pra você deve valer muita coisa. Tente vender, vai valer só uma fração do que você acha, “se” tiver alguém que quer comprar.

Aí o cara preguiçoso que corta caminho pode parar pra pensar, por exemplo, "ah, vou parar de fazer testes, é o tipo de coisa que ninguém vê que existe, eu gasto mais tempo pra fazer, no final é tudo a mesma coisa". Lembram quando eu falei que 2 + 2 é 4 independente se alguém disser que é 5? Manter a qualidade do seu próprio trabalho é a mesma coisa: independe de sinais externos. Se alguém disser que não precisa fazer, você sabe que tem que fazer. Porque ao longo do tempo, se seu código é o que sempre causa bugs, sempre volta pra consertar; o que vai quebrando com o tempo é sua reputação. Por outro lado, se você faz o que ninguém pediu que é manter a qualidade, e sempre seu código funciona de primeira, nunca volta, e ninguém nunca elogia mas também nunca critica, ao longo do tempo sua reputação de confiável é que aumenta. Qual dos dois você acha que com o tempo tem mais chances de subir de carreira?

Esses são alguns tipos de micro-decisões que você vai exercitar ao longo do período entre júnior e pleno. O sênior pra ser sênior primeiro precisa ser de confiança, tanto do ponto de vista de integridade quanto de qualidade do que faz. Aqui a intuição falha a maioria das pessoas. Muita gente associa gente que fala bem com gente que entrega bem. Nunca ouça a opinião de pessoas sem skin in the game, eu já disse isso antes. Mais do que fazer um bom código, que é meio esperado, um sênior é sênior porque eu deveria poder confiar nas decisões que ele toma. O valor de um sênior começa na sua capacidade de tomar de decisões - que se não ficou claro, também implica que ele assume a responsabilidade da decisão. Decidir sem ter o ônus do fracasso não serve pra nada, não tem skin in the game.

De nada adianta um programador que sabe fazer um código bonito, eficiente, se ele é um péssimo tomador de decisões, ou pior, sequer consegue se decidir e fica em cima do muro, ou posterga, ou empurra com a barriga. Isso eu espero de um júnior. De um sênior se espera que ele tome a decisão certa na hora certa, e ninguém precisa pedir. A gente pede pro júnior. O sênior pede pro júnior. E essa é a outra coisa que um sênior esperto sabe fazer: delegar e orientar.

Eu sei que um cara é sênior quando ele aprende que eficiência em código tem um limite. Às vezes, em situações específicas, um júnior pode até fazer um código melhor e mais rápido que um sênior. Mas o sênior esperto é o cara que consegue pegar dois júniors do seu lado, e com as orientações certas, eleva o resultado desses júniors mais próximo do que se esperaria de um pleno. Ou seja, o sênior de verdade sabe escalar horizontalmente. E quanto ele sabe fazer isso é quando chegamos no famigerado conceito do desenvolvedor 10X.

Tem duas formas de se encontrar um desenvolvedor 10 X, em situações técnicas especiais, onde estamos falando de um sênior especialista, o cara que consegue criar um novo protocolo de rede, um novo garbage collector, um novo classloader, numa situação técnica incomum pra maioria das empresas, e sozinho, com pouco código até, faz de conta, consegue eliminar o uso de 90 máquinas das 100 que você precisava antes. Normalmente você precisa de escala e situações especiais pra ter essa oportunidade, por isso você não vê todo dia.

Mas mais comum é o caso do sênior que escala horizontalmente, e consegue fazer sei lá, 5 júniors performarem como 5 plenos. Eu disse no começo que pra mim um junior, um pleno ou um sênior são capazes de entregar a mesma qualidade de código, a diferença é o tempo que eles vão levar, proporcional à inexperiência. Se o sênior, com 2 tentativas consegue achar a melhor solução, o júnior precisaria testar 20 jeitos diferentes, porque ele nunca fez antes. Mas se o sênior ajuda ele a eliminar as 18 opções que ele já sabe que não funcionam, o júnior consegue focar só nas 2 que que tem mais chances de funcionar, e o resultado vai ser ordens de grandeza melhor.

Muita gente pergunta sobre a especialização. Eu não considero isso algo fácil de escolher, especialmente no começo. Ao longo do tempo, depois que você deixa de ser júnior e passa algum tempo como mid-level, acho que naturalmente você vai indo pra uma direção ou pra outra. Alguns simplesmente não tem capacidade de orientar os outros, mas se tiver a sorte de encontrar um ambiente que precise de especialistas, ele pode dar certo assim também. Mas como eu disse antes, a maioria dos lugares fora do 1% não tem tantos problemas nessa escala pra serem resolvidos, algo que exija alguém desenhar uma nova linguagem por exemplo. Sendo prático, na maioria dos lugares, o ganho maior vem da escalabilidade horizontal.

Não significa virar um gerente, não é delegar 100% do seu trabalho, é orientar, é fazer parte do código e delegar o restante, é ser alguém que consegue mostrar em código uma prova de conceito ou um modelo pra seguir. É o perfil de alguém que naturalmente viraria o que hoje se chama de tech lead ou líder técnico, que tem a visão do que precisa ser feito e com isso vai delegando e encaixando as peças, testando, pesquisando e orientando os demais.

Mas existem outras categorias e alguns anti-patterns aqui. Você precisa ter muito cuidado pra não se tornar um cavaleiro solitário, o cara que trabalha sozinho, que não aguenta ouvir críticas, que não muda de posição e toma decisões erradas e se recusa a aceitar que tomou decisões erradas. Esse tipo rapidamente vai entrar no time do Go Horse. E dependendo do tipo de empresa que entrar, especialmente nas pequenas onde não tem ninguém muito melhor, vai virar o herói e o vilão ao mesmo tempo, consertando os bugs que ele mesmo criou. E vou dizer que existem centenas nessa posição.

Outra categoria tão ruim quanto. Em empresas que crescem muito rápido, particularmente tech startups, é muito fácil um procrastinador se esconder. Como os processos mudam toda hora, a estratégia muda toda hora, é até difícil pra equipe notar que um dos membros está se escondendo ou mesmo sabotando. Alguns podem até notar, mas como as prioridades mudam toda hora, ninguém quer nem discutir. E como é muito comum a atitude clichê do "não vamos procurar culpados", é um excelente ambiente pros maus profissionais se esconderem e até se tornarem bem sucedidos, especialmente se for o tipo que fala bem. Ele passa rapidamente nos ranks de programação e consegue encontrar uma oportunidade de liderança onde não precisa codar, e como ele mesmo não tem parâmetro técnico pra medir as pessoas, ele vai medir pelos parâmetros errados do tipo, se ele se dá bem com a pessoa ou não. E assim a qualidade técnica de uma empresa que não muito tempo atrás era até boa apesar do caos, rapidamente se torna ruim.

Pra fechar a cagação de regra, por isso eu falo pra esquecer metodologias, processos, métricas e tudo mais no começo. Dado tudo que eu falei, se tem uma recomendação que eu faria primeiro a qualquer empresa, de qualquer tamanho, é que nenhum código deve ser imune à revisão, não importa de quem seja. Todo código deve ser revisado por alguém da equipe, de preferência mais de uma. Todo júnior precisa que alguém aponte o que ele fez de errado, o mais rápido possível. Todo sênior precisa se acostumar a orientar os outros e revisar o código é o primeiro passo. E pra escalar não tem nada melhor que pressão peer to peer, todo mundo olhando todo mundo. Se isso for rotina, fica muito fácil pra equipe inteira notar muito rápido quem está entregando código, em qual qualidade e com que frequência, e não deixar os problemas graves se acumularem a níveis ingerenciáveis.

E daí basta ser consenso que ninguém quer trabalhar do lado de alguém que está fugindo de entregar o que devia e está bloqueando e atrapalhando a equipe inteira, porque ninguém gosta de toda hora ter que ficar consertando o erro dos outros, nem de estar num ambiente com pessoas ruins. Portanto, antes de qualquer tipo de ritual, qualquer tipo de métrica, simplesmente institucionalize a revisão de código geral. A regra é simples: quem fez o código não pode mergear ela na master. Coisas como falta de testes, gambiarras e falta de cuidado em geral começam a aparecer bem mais rápido.

Ahh Eu sei que já ficou bem longo, mas eu preciso conectar com outro parâmetro que eu acho importante. Eu mencionei mercado, eu falei sobre os cursos de faculdade, os níveis de experiência mas não falei sobre o contexto, mas prometo que vai ser rápido. Entenda o seguinte: toda tecnologia tem um ciclo de adoção, e pra variar é outra curva em S. Toda adoção tende a começar devagar, se ela consegue pular o penhasco dos early adopters, ela vai entrar na early majority e começa a acelerar rápido. Aqui já ficou óbvio e você começa a correr pra não ficar pra trás. Uma hora ela atinge um pico de adoção e a partir daí o crescimento acelerado pára e vai indo bem mais devagar e em alguns casos começa a decair.

Neste exato momento tem tecnologias que já estão no fim da curva, e costumam ser as mais populares que você acha que ainda vai durar muito tempo. Mas quando já tem dezenas de cursos, eventos, posts, e todo mundo fala nela é porque ela já passou do early majority e virou mainstream. Pode já estar no fim do Late Majority. Quem sempre vai ganhar mais é quem souber “surfar” como eu vivo dizendo. É quem pega pelo menos no final do early adopter e salta pro early majority já tendo experiência. Enquanto todo mundo tá começando a aprender, você já aprendeu e já sabe usar então você é quem tem mais valor no mercado nesse momento. No pico da curva é quando cada nova pessoa entrando vai valendo menos e menos, e uma hora a curva inverte, e se você cometer o erro de entrar em negação e ficar defendendo tecnologia em curva invertida, vai ser que nem quem defendia Blackberry ou Windows Mobile em 2011. Quem saltou e pegou o early adopter de iPhone em 2009 e aprendeu Objective-C se deu muito bem porque o pico foi entre 2010 e 2014, desde então a curva desacelerou já.

No mundo ideal você sempre gostaria de ser o especialista de alguma coisa antes dela chegar no pico de adoção. No mundo real é difícil de adivinhar quais vão ser as tecnologias da próxima curva. Então eu pessoalmente sempre gostei de ser pelo menos um pleno na tecnologia que entrou em early majority e early adopter em algumas que estão no início da curva. E repetindo isso a cada nova curva. Daí se uma das que eu escolhi aprender, com sorte, entrou no early majority e eu já sei, posso surfar nela de novo. E a que eu era pleno, com sorte, ainda existe e agora eu sou sênior nela. E agora não posso descansar, preciso ver quais vão ser as próximas. São ciclos que duram cerca de 10 anos, então de 5 em 5 preciso ver alguma coisa. Eu aprendi Objective-C em 2010 por exemplo, e nessa época eu era pelo menos um pleno em Ruby, que estava no pico da sua curva.

O grande segredo óbvio é que você nunca vai ser totalmente pleno e nem totalmente senior. Você pode ser sênior de uma coisa, mas junior em outra. E isso vai variando ao longo do tempo. Aposte todas as fichas na coisa errada e você vai ser expert de uma tecnologia morta, que pode não significar muito em pouco tempo. Se isso tá parecendo mercado financeiro, você não tá muito errado.

Se você for conservador demais, vai deixar tudo na poupança e não mexer, achando que tá seguro, sem saber que você cresce menos que a inflação e ao longo do tempo na verdade você está deteriorando e cada vez valendo menos e menos e com cada vez menos chances de recuperaçao. Se você for hypeiro demais e colocar tudo numa ação arriscada ou IPO, já já você pode perder tudo ou muito mais que gostaria. Se quiser balancear você vai ter uma parte em renda fixa, outra parte em ações, e uma pequena parte em opções. Eu penso em carreira mais ou menos da mesma forma. Não é uma escolha fixa, por isso eu mesmo não me considero expert em nada, minha estratégia foi ir diversificando, meio que por acidente, meio chutando, mas aos poucos fui acertando.

Sobre ganhar mais ou menos, você vai ter que aprender a negociar. Pra negociar você tem que ter alguma coisa a oferecer. E repetindo pela última vez: o que você “acha” que você vale não é o preço final, é o preço inicial da negociação. Tudo na vida é negociável, só que se você não tem disposição pra tomar uma decisão, alguém vai decidir por você. Entenderam porque eu disse que não terceirizar decisões é a coisa mais importante? E também como em finanças, existem decisões que vão dar certo e existem decisões que vão dar errado e você vai ter algum prejuízo. Use de aprendizado e não erre na mesma coisa de novo.

Tem muito mais coisas que eu posso falar sobre esses temas mas acho que por hoje já é bastante coisa. Se vocês tem experiências diferentes e outras perspectivas, não deixem de mandar nos comentários abaixo. Se curtiram o vídeo mandem um joinha, não deixem de assinar o canal e clicar no sininho pra não perder os próximos episódios. A gente se vê, até mais.


[Akitando] #62 - Aprendendo "Fotografês" | Brinquedos de Miami

DESCRIPTION

Semana passada eu fui pra Miami e resolvi trazer alguns upgrades de equipamento pro canal. No caminho tive que aprender mais algumas coisas sobre fotografia que eu só sabia na teoria mas nunca tinha praticado.

O vídeo de hoje vai ser uma introdução a esses conceitos de fotografia e como eu configurei os brinquedos novos de Miami pra criar um novo "look" pro canal (que só vou mostrar mais pro fim do vídeo, claro)

Semana que vem também vou viajar de novo, então perdoem-me por estar semana sim, semana não com vídeos. Já já vamos voltar ao normal.

Links:

  • THE BEGINNER’S GUIDE TO USING GRIDS WITH SOFTBOXES (https://www.diyphotography.net/beginners-guide-using-grids-softboxes/)
  • What is ISO? Camera settings explained (https://www.digitaltrends.com/photography/what-is-iso/)
  • Understanding Aperture in Photography (https://photographylife.com/what-is-aperture-in-photography)
  • Introduction to Shutter Speed in Photography (https://photographylife.com/what-is-shutter-speed-in-photography)
  • What is ISO? The Complete Guide for Beginners (https://photographylife.com/what-is-iso-in-photography)
  • Photography 101: Crop vs. Full Frame Cameras (https://thewirecutter.com/reviews/the-first-canon-lenses-you-should-buy/)

  • Canon EOS Rebel T7i / 800D Specifications (https://www.the-digital-picture.com/Reviews/Camera-Specifications.aspx?Camera=1111)

  • Canon 5D Mark III vs Sony A7 II (https://www.apotelyt.com/compare-camera/canon-5d-mark-iii-vs-sony-a7-ii)
  • Should I Get the Canon EOS 6D Mark II or the 5D Mark III? (https://www.the-digital-picture.com/News/News-Post.aspx?News=20836)
  • FIVE REASONS TO CONSIDER F/1.8 OVER F/1.4 LENSES (https://www.diyphotography.net/five-reasons-to-consider-f-1-8-over-f-1-4-lenses/)

SCRIPT

Olá pessoal, Fabio Akita

Semana passada eu tava viajando, acabei de voltar de Miami. Semana que vem estarei no sul, então vai ter outra semana de intervalo. Este mês tá mais complicado que o normal, acabou juntando viagens e palestras tudo na sequência. Paciência que em breve volto ao normal aqui no canal.

O episódio de hoje vai ser mais simples. Aproveitando que fui pra Miami resolvi tentar fazer mais alguns upgrades aqui pro canal. Se você assistiu meu episódio de um ano que soltei no fim de Agosto já tem uma idéia de como eu produzo meus vídeos. Mas eu ainda queria fazer algumas mudanças. Meu cenário é bem claro como vocês podem ver e eu queria mudar pra um cenário mais escuro, até o fim do vídeo vamos ver se consigo fazer isso.

Hoje vou ser mais geek sobre o equipamento e o que eu resolvi comprar pra ver se melhora um pouco mais a qualidade técnica dos vídeos e também consigo facilitar um pouco minha vida pra gravar e editar. A imagem final não necessariamente vai ficar melhor, mas diferente e eu vou explicar o processo do que eu queria fazer.

O vídeo de hoje vai repetir algumas coisas que já contei no vídeo de um ano do canal pra dar contexto. Pra variar, não vou só mostrar as coisas, quero aproveitar pra registrar o que eu aprendi pra não esquecer. Lembrando que eu não sou fotógrafo profissional, nem tenho treinamento em fotografia, tudo que eu sei eu aprendi sozinho e por isso eu sei que ainda tem várias coisas que eu não sei. Se tiver fotógrafos mais experientes assistindo, não deixem de me mandar mais dicas nos comentários! Enfim, em agosto de 2018 eu ainda não sabia se ia ou não conseguir mesmo manter a frequência necessária pra se ter um canal decente com vídeos no mínimo semanais. Por isso fui colocando equipamentos aos poucos. Eu comecei usando esta DJI Osmo, nenhum motivo em especial, só porque eu já tinha ela. Se não tivesse estaria usando meu celular mesmo, com um tripezinho vagabundo qualquer de 50 reais.

Mas depois de meia dúzia de vídeos, eu senti que comecei a pegar o ritmo, daí achei que era hora de comprar uma câmera de verdade. E sim, câmeras de verdade são BEM diferentes de câmeras de celular, mesmo do seu melhor iPhone 11 Pro ou Huawei P30 Pro. Antes de continuar, vamos repetir o que todo profissional deve estar cansado de repetir: megapixels não significa imagens melhores. Entre uma câmera de sessenta e oito megapixels com um sensor bosta versus um sensor de doze megapixels com um sensor decente, obviamente sempre escolha o sensor decente. Por isso, pelo custo benefício, eu escolhi a Canon Rebel T7i que em outros mercados também é chamada de 800D. Ela é uma câmera de vinte e quatro megapixels, mas ela é crop, não é full frame, é o tal custo benefício que eu falei.

Muitos youtubers melhores do que eu usam uma linha acima da Rebel, como as Canon 5D Mark III ou Canon 6D Mark II que em termos de megapixel é um pouco menos que a Rebel de vinte e quatro pra vinte e dois megapixels se não me engano. Porém, a 5D e 6D são full-frame. Além disso a Rebel grava vídeos de 1080p e a 6D grava 4K. A diferença é que a Rebel T7i custa 800 dólares e a 6D Mark III custa 1500 dólares, é quase o dobro de diferença.

Mas a escolha não foi só o preço. Se eu gravar em 4K só vai aumentar o peso pra edição e o tempo pra processar e exportar o vídeo depois. Ou seja, vai aumentar meu trabalho sem ganhos significativos. Na prática, existem duas grandes vantagens de se gravar em 4K, a primeira é que depois que você faz downsampling de 4k pra 1080p, a imagem vai ficar mais nítida. A segunda vantagem é que eu posso fazer zoom em alguns shots e não perder resolução se eu estabelecer a base de 4K, porque daí consigo fazer zoom de até 2 vezes sem as desvantagens de supersampling.

YouTubers, vamos dizer, semi-profissionais como um Peter McKinnon ou Casey Neistat usam algo como a Canon 5D Mark II ou a Sony A7 II. Na verdade, se você for investir em algo como a 5D eu altamente recomendo ver a Sony A7 II, ela é menor, mais leve, e pra coisas como vlog tende a ser melhor porque ela tem in-body stabilization, enquanto as Canon dependem de estabilização que vem na lente.

Youtubers, vamos dizer, mais profissionais, como o pessoal da Linus Tech Tips, MKBHD, Corridor Digital, usam o topo de linha das câmeras como as Hasselblad, ou BlackMagic que é a mesma que faz o editor DaVinci Resolve, ou as lendárias RED. São câmeras superiores que gravam 6K a 8K, mas uma Hasselblad acho que não tem por menos que 15 mil dólares, as BlackMagic já são mais acessíveis começando lá pelo 1,400 dólares mas você vai precisar de acessórios caros. Já a RED você precisa comprar o Brain que é o núcleo da câmera mais diversos acessórios também como os Mini-Mags de SSDs, o monitor, baterias. Algo como o Gemini Camera Kit pra começar não sai por menos que 27 mil dólares!

Definitivamente são câmeras pra quando se tem 1 milhão de inscritos pra mais com muitos patrocinadores. Como eu disse antes, não é só a câmera, fora o tanto de acessórios caros que essas câmeras exigem, agora você tem um workflow que exige editar videos em 8K que dependendo do codec como o REDCODE ou Cinema DNG ou Apple ProRes e da taxa de compressão pode ser algo na faixa de 200 GB por hora de vídeo e você precisa dar um jeito de editar e processar isso. Agora você precisa de uma workstation realmente potente, ainda mais potente do que o meu que eu já considero muito bom.

Esse é o teto que dá pra chegar, acima disso seriam câmeras como as Arri Alexa que Hollywood usa, mas tecnicamente falando, na grande escala das coisas, eu sou ainda um canal hiper pequeno, então a escolha de equipamentos tem que levar isso em consideração. Voltando ao que eu comprei, pra vídeos curtos, só o que vem na Canon Rebel seria suficiente, mas ultimamente eu ando tendo problemas. Meus vídeos estão começando a estabilizar em 40 minutos pra cima, alguns indo pra cima de uma hora. A bateria de uma Canon eu acho MUITO ruim, eu acho que ela não aguenta mais que uma hora de vídeo. Algumas vezes eu estou gravando, e dependendo do script eu acabo errando e esticando e pode chegar a 2 horas ou mais que eu preciso cortar depois, mas aí chegando perto do fim a bateria não aguenta e a câmera desliga. E quando isso acontece eu fico assim: (XINGANDO)

Primeiro que ele corta o que eu tava falando, daí eu tenho que regravar esse trecho. Mas o que mais me incomoda, pra trocar a bateria eu preciso mexer na câmera, e mesmo tendo cuidado é muito difícil manter a câmera exatamente na mesma posição. Olha só o mecanismo pra abrir o compartimento da bateria.

Esses cliques vão tirar a câmera da posição e se você estiver prestando atenção vai notar quando o cenário desalinha de um shot pra outro. Mesmo que vocês não notem, eu noto, e isso me incomoda muito. Então pra resolver isso comprei esse grip com compartimento pra duas baterias! Pro tamanho dos vídeos que eu gravo, duas baterias é mais que suficiente pra ir até o fim sem eu precisar trocar no meio e isso já me alivia muito!

O próximo item é a lente. As lentes não são só pedaços de vidro num case. As lentes da Canon são motorizadas pra ter estabilização e auto foco, ou seja o que eles chamam de AF Motor. E aqui vem o primeiro erro que eu fiz na minha compra, vocês vão dar risada. Eu jurava que estava usando a 50mm f/1.8 e por isso comprei uma 50mm f/1.4. Porém só quando voltei que fui ver que na verdade eu já estava usando a f/1.4 então no final acabei ficando com duas lentes …. Puta burrice. De qualquer forma, vou aproveitar a deixa pra falar um pouco sobre o que aprendi sobre lentes.

Lentes 50mm dizem que é uma das melhores pra coisas como portrait ou retrato mas mais do que isso ela é boa porque parece que é a que mais se assemelha com como nossos olhos vêem então deve ser mais natural. A lente que vem com a câmera também pode ser ajustada pros 50mm já que ela tem um range de 18-55 mas o f-stop dela é maior em f/4 ou seja, não é boa pra ambientes pouco iluminados, daí aqui no meu quarto eu sou obrigado a aumentar o ISO ou o shutter speed. Vou falar disso já já.

Aliás, se você queria saber a diferença de full-frame e crop, ou seja, entre a 5D e a Rebel T7i, é que a lente de 50mm na minha câmera na realidade seria como uma lente 80mm numa full-frame, ou seja, quase um short telephoto. Agora fodeu né? Relaxa, não é tão difícil assim. Deixa eu tirar a lente da câmera.

Olhem no centro, este é o sensor da câmera. Esta é a parte mais importante da câmera, aliás, NUNCA, JAMAIS enfie seu dedo nojento e gorduroso em cima do sensor. É este sensor que vai se sensibilizar com a luz e captar a imagem. Ela é que define os megapixels, por exemplo. Quanto mais luz cada pixel receber, melhor vai ser a imagem, essa é a regra. Por isso mais megapixels não se traduzem em imagem melhor se ela não recebe mais luz por pixel. Veja o tamanho de um celular em comparação, o sensor dela é muito menor, ou seja, a área física que recebe luz por pixel é muito menor.

Numa câmera full-frame, de cara você vai ver que o sensor é maior. Fora isso a câmera se chama "crop" porque ela faz um crop do frame completo. Por isso cada fabricante tem um fator de multiplicação, no caso da Canon é 1.6 então você precisa multiplicar a distância focal, no caso de 50mm, por 1.6 e por isso eu disse que a 50mm na realidade seria equivalente a uma lente 80mm numa full frame. Aliás, distância focal é a distância da lente pro sensor.

E isso é outra coisa que me confundia no começo. Maior distância focal não é só zoom. Existe diferença entre zoom e telephoto. Zoom pode ser wide-angle ou telephoto. Mas a maioria das pessoas acaba chamando quando tem mais milímetros de telephoto. Eu achei um exemplo bom no Quora pra explicar.

300mm é telephoto mas não é zoom porque ele tem longa distância focal mas não cobre um range de distâncias focais, por exemplo, não dá pra ir pra 250mm ou 400mm, é o que se chama de lente prime, que só cobre uma distância focal.

Uma lente 10-20mm não é uma lente telephoto mas é zoom, porque cobre um range de 10 a 20 mas com baixa milimetragem, por isso é wide-angle. Uma lente de 18mm não é nem telephoto e nem zoom porque é de baixa milimetragem e não tem range. Poderíamos chamar de lente prime wide-angle.

Já uma lente tipo 18-200mm é estranho. Ela é uma lente com zoom porque tem range mas pode ser considerada tanto wide-angle a 18mm quanto telephoto a 200mm.

Enfim, telephoto é bom pra esportes por exemplo, quando você tá na arquibancada e quer tirar a foto da cara do jogador em campo. Por isso você vê aquelas lentes gigantes na frente da camera, porque ele precisa de uma distância focal enorme pra conseguir enxergar tão longe. E por isso nenhum celular, por si só, tem capacidade de telephoto.

Pra dar uma idéia, a câmera da minha Samsung S10e só tem 12mm com f/2.2 de abertura. Aliás, é hora de falarmos de abertura, porque é grande diferença em lentes, eu achava que estava com a f/1.8 por exemplo mas era a f/1.4. A primeira coisa que sempre me confundiu é essa maldita medida de f-stops. Na prática aperture ou abertura é exatamente o que o nome diz, a abertura do shutter ou objetiva que é por onde a luz vai passar quando você aperta o botão de shutter. Quanto maior, mais luz passa.

No artigo que vou deixar linkado nas descrições abaixo você tem uma explicação melhor e mais detalhada mas na prática pense na objetiva fechada. Agora divida a área por esse número de f-stops, por exemplo, na lente padrão acho que ela é f/4 então a abertura seria um quarto dessa área. Se fosse uma lente f/16 seria 1 dezesseis-avos, ou seja, um buraquinho bem pequeno por onde passa pouca luz. Então num f/1.4 seria a área dividida por 1.4, ou seja, um buraco bem maior do que a metade da área por onde passa mais luz.

Então, um f-stop grande significa uma abertura menor, menos luz, e um campo de profundidade maior, em inglês seria large depth of field, onde você vê nítido tanto objetos próximos quanto objetos à distância como prédios ou montanhas. É bom pra ambientes iluminados, fotos de natureza, com muito sol. Daí a abertura tem que ser menor, ou a imagem vai ficar clara demais e vai estourar os brancos por causa de exposição demais à luz, que é o que chamamos de over-exposed.

Quando tiver um f-stop pequeno, como essa minha lente de f/1.4 a abertura é bem maior, então entra o máximo de luz possível, e também oferece um campo de profundidade mais raso, em inglês seria o tal shallow depth of field, onde você tem objetos próximos mais nítidos mas tudo no fundo fica blur, borrado, que é o tal efeito bokeh ou bokah que os celulares começaram a oferecer com o tal modo retrato ou portrait.

Só que em celular é um blur artificial feito digitalmente recortando a pessoa e aplicando blur digital no fundo, por isso o efeito é normalmente ruim se comparado a uma lente de abertura grande de verdade. E com abertura grande significa também que entra mais luz, mesmo em ambiente com pouca luz, como fotos durante a noite ou em ambientes fechados e pouco iluminados. Novamente, os celulares oferecem o tal night mode, que é outro processamento digital tirando várias fotos e recombinando pra conseguir extrair o máximo de detalhes mesmo com pouca luz. Aberturas grandes custam mais caro e por isso camera de celular normalmente são f/2 ou no máximo f/1.8.

Isso é um dos motivos de porque recomenda-se a lente 50mm f/1.8, pois ela tem abertura grande suficiente pra maioria das fotos em lugares menos iluminados e oferece o tão desejado efeito bokeh de verdade, sem processamento digital. Uma lente dessas tá na faixa dos 100 dolares. Agora qual a diferença desses 0.4 f-stops? 200 dólares! É isso mesmo, uma lente 50mm f/1.4 custa uns 300 dólares.

Deixa eu fazer outra tangente em fotografia. Exposição de fotos estão fundadas em três grandes pilares: aperture, ISO e shutter speed. Aperture eu acabei de explicar, basicamente objetiva que abre mais ou menos e deixa entrar mais ou menos luz. ISO varia de 100 a 12000 ou 50 mil em lentes mais caras ou 100 mil em lentes mais caras ainda. Mas ela é tipo você ir no Photoshop e aumentar o brilho ou exposure, você consegue tirar fotos em lugares escuros e aumentar o ISO se a imagem estiver muito escura, só que a contrapartida é que quanto mais você aumentar o ISO mais cheia de noise e grain ela vai ficar, sabe aquela sujeira digital que aparece em imagens escuras?

Pois é, idealmente você quer estar o mais próximo possível de ISO-100, raramente você quer subir pra mais que ISO-1000, eu não sei onde ISO acima de 12 mil é útil porque eu não sou fotógrafo, mas no meu caso acho que eu estou em ISO 600 ou 800 e pro meu caso acho que isso já é limite.

Quanto maior for a abertura, ou seja, quanto menor for o f-stop como meu f/1.4, menor pode ser o ISO e melhor vai ser a imagem, em ambientes escuros. Em ambientes claros uma abertura menor, ou seja f-stop 2 ou acima vai deixar entrar luz suficiente e também seu ISO pode ser menor. Idealmente, se você vai filmar ou tirar fotos em ambientes escuros, de noite principalmente, menor tem que ser o f-stop da lente, então esta minha lente 50mm f/1.4 seria boa pra tirar fotos mais nítidas a noite, evitando eu ter que subir o ISO demais.

A terceira variável é shutter speed. Aqui depende do efeito e da situação. Shutter speed é quanto tempo a objetiva vai ficar aberta coletando luz. Ela é medida em frações de segundo como 1 dividido por 4000, ou seja a objetiva fica aberta por 1 quarto de milisegundo. Shutter speed faz mais diferença quando você tá tirando fotos ou filmando objetos em movimento, tipo uma corrida, ou animais ou pessoas e você quer que apareça o blur do movimento na imagem em vez de congelar o movimento e tirar uma foto nítida de um instante do movimento.

Quanto mais lento for o shutter speed, ou seja, quanto mais tempo a objetiva fica aberta, mais desse movimento é registrado em cada frame, e isso causa blur de movimento. Quanto mais rápido for o shutter speed mais nítido é a imagem em movimento e menos blur. Aqui eu não tenho ainda muita sensibilidade de qual o melhor shutter speed pro meu caso, mas na prática, quanto mais rápido for o shutter speed, maior você quer que seja a abertura, porque o tempo pra luz entrar vai ser menor. Então a lente f/1.4 teoricamente deve ser melhor pra conseguir imagens mais nítidas de coisas em movimento também, onde você vai querer shutter speeds cada vez mais rápidos, sem aumentar o ISO.

Em resumo, você quer o menor ISO possível, sempre. Você quer a maior abertura possível, ou seja, menor f-stop, principalmente se quiser tirar fotos a noite, e você quer escolher shutter speeds rápidos pra imagens mais nítidas a menos que queira um efeito mais artístico de blur de movimento. As câmeras tem diferentes modos pra te ajudar, no padrão ele vem no modo automático, onde a câmera vai escolher shutter speed e aperture automaticamente pra você.

Pra maioria dos casos simples isso deve ser suficiente pra amadores como eu. Daí existe o modo manual, onde você deve escolher tudo manualmente, ISO, shutter speed, aperture e outras configurações de balanço de cor e tudo mais. Mas existem modos intermediários como o modo AV que é o modo Aperture-priority autoexposure, onde eu posso escolher o aperture manualmente e a câmera vai escolher o shutter speed e eu acho que vou testar mais esse modo, assim como também tem o modo TV que é Shutter-priority Autoexposure, onde eu poderia escolher o shutter speed e a câmera escolhe o aperture mais adequado.

Eu sou amador então eu estava usando o Creative Auto, que é um pouco menos automático mas eu deixo a câmera escolher o shutter speed e aperture. Como eu estou usando um softbox com muita luz e vocês podem ver que o ambiente fica bem claro, então mesmo no modo automático a câmera não precisa subir o ISO e por isso não fica com sujeira ou noise na imagem. Eu acho que ele deve manter o shutter speed por volta de 60, ou seja 1 sessenta-avos de segundo a um ISO de uns 200 usando o f/1.4 máximo da lente. Nas modificações que estou testando vou começar a usar modo manual mesmo e já já vou explicar como estou configurando tudo isso.

O desafio é escolher o exposure, a exposição adequada, ou seja, quanto de luz vai atingir o sensor e como. Quanto mais luz é melhor até o ponto onde não fique over-exposed. Mas você quer que o tempo de exposição seja o mais curto possível pra ter imagens nítidas. Falando em estático, quanto mais devagar for o shutter speed menos você pode mexer a câmera e vai precisar de um tripé. No meu caso eu quero evitar blur o máximo possível. O correto na realidade não é forçar os limites da câmera e da lente pra sugar a pouca de luz que eu tenho no meu quarto. Em vez disso a coisa mais óbvia que se precisa num ambiente de pouca luz é colocar mais luz. E agora a coisa fica mais complicada.

Você não quer qualquer luz, você quer luz soft e direcionada, ou seja, key light através de um difusor. Manipular a luz é uma ciência em si só. Desde o começo eu venho testando algumas luzes, mas na maior parte dos episódios do canal eu estou usando este softbox barato que comprei no mercado livre, e hoje ela tem quatro lâmpadas brancas fluorescentes de uns 1000 lumens cada. Isso seria mais ou menos equivalente a uns 16 watts por lâmpada. Existem diversos tipos de lâmpada, como lâmpadas incandescentes de tugstênio, halogênio, LED e assim por diante.

Quando se fala em luz pra estúdio, a maioria dos YouTuber semi-profissionais vai automaticamente dizer Aputure. É uma marca altamente reconhecida e toda vez que eu pesquiso esbarro neles. E o produto mais recomendado é o Aputure Light Storm C120D II, um canhão de LED voltado pra temperatura de luz do dia a 5500 kelvin e com capacidade de iluminação de 135 mil Lux. É a luz perfeita pra ser key light de qualquer YouTuber, especialmente se combinar com filtros opcionais como Fresnel.

O problema é que ela custa nada menos que uns 700 dólares. Assim como eu não preciso ainda de câmeras Blackmagic ou RED eu também ainda não preciso de uma Light Storm. Só se um dia eu decidir usar um estúdio mesmo. Felizmente existem outras opções, e eu achei a marca Godox. A mais ou menos equivalente à 120D II seria a Godox SL-200w.

A 120D tem LEDs de 180 watts e a Godox tem LEDs de 200w e custa quase a metade do preço, entre 300 a 400 dólares. Mas mesmo assim eu não preciso de tanta luz assim, as minhas pobres lâmpadas fluorescentes são só uma fração disso e iluminam razoavelmente bem. Aliás, as Aputure tem lâmpadas de tungstênio e LEDs, acho que a 120D é LED e a 120t é tungstênio ou algo assim, mas posso estar dizendo besteira.

Por isso eu escolhi esta Godox SL-60 watts, que deve ser mais ou menos equivalente às lâmpadas que já tenho. Isso aumenta minhas opções de trabalhar a luz a 100 dólares. Nada mal. E isso porque meu espaço é pequeno. Mas mais importante do que só a luz é o difusor e por isso eu resolvi parear essa Godox com um domo difusor da Aputure, o Light Dome Mini II. De novo, o ideal seria a versão maior, mas eu não ia conseguir trazer na mala então escolhi a mini mesmo por agora. O que eu achei interessante foi que eles incluíram o grid pra colocar na frente do difusor.

Aqui mais um pouco de tangente pra explicar. A luz que sai de uma fonte como uma lâmpada ou LED, pense nela como se fosse uma substância saindo em forma de jato direcionada pra frente. Quando ela bate em você ela vai projetar uma sombra bem definida, dura, ou "hard". Pra situações como a minha, luz na minha cara diretamente, isso destaca demais os defeitos faciais, buracos na pele, espinhas, ou qualquer coisa que torna a superfície irregular. Ela define suas características de forma muito dura, sombra do nariz, da boca, do pescoço. Luz hard é uma luz que mostra tudo, mais do que devia.

Pra melhorar essa cara feia aqui, começamos colocando um modificador. Modificadores são coisas como um Fresnel, space light, barn doors, no meu caso começo com o Light Dome e na frente dele um difusor. Assim o tal jato de luz que sai da lâmpada reflete no modificador e é quebrado no difusor que vai dividir essa luz pra tudo que é direção diferente.

Ela diminui a intensidade desse jato e ao mesmo tempo aumenta a área de iluminação porque agora a luz vai pra tudo que é lado, e por isso se diz que a luz vai ao seu redor, e com isso as sombras se tornam mais suaves e todos os seus defeitos também se tornam mais suaves, e por isso você pensa que eu sou muito mais novo do que eu realmente sou, porque essa luz apaga muitos defeitos. E é assim que eu venho filmando até agora.

Mas pro meu caso, o problema da luz ir pra tudo que é direção, como o meu softbox antigo também já fazia, é que ela ilumina meu cenário inteiro. Isso pra mim sempre foi um inconveniente porque eu acho que meu ambiente fica meio boring, chato, sem contraste entre as camadas da frente e de trás. Por isso eu coloquei os leds coloridos lá atrás pra deixar o fundo um pouco mais interessante.

Mas existe um segundo item que eu posso colocar na frente do difusor, que é um grid, e a função do grid é devolver controle, pra onde a luz vai iluminar. Ela ainda vai ser uma luz soft mas agora direcionada mais pra mim e menos pro fundo. Em teoria, no meu caso, isso deveria me destacar mais do que o fundo, criando duas camadas na composição. Além de colocar o grid que já veio no Light Dome Mini, eu também resolvi trocar o tripé que eu estava usando por um tripé girafa. A idéia é que em vez da luz ficar na minha frente, eu quero colocar ela mais pra cima de mim, dessa forma a luz vai ficar direcionada mais pra mim e menos pro resto do ambiente. Então deixa eu trocar meu softbox antigo por esta nova configuração e vamos ver no que dá.

(TESTE DE LUZ)

O que acham? Acho que todo fotógrafo e videógrafo profissional vai concordar que não tem nada mais importante do que o key light, e eu acho que a combinação desse Godox SL-60w com o Aputure Light Dome Mini deve ser o upgrade mais interessante até agora, embora isso tenha deixado minha configuração mais complicada e a qualidade da imagem não necessariamente tenha ficado melhor. O problema é que do ponto de vista da lente, o ambiente ficou de fato mais escuro. Como eu já estava usando o máximo da abertura em f/1.4, pra não subir o ISO e manter ela em 100, eu fui obrigado a diminuir o shutter speed de 60 pra 30.

Eu passei horas e horas testando essa configuração, mantendo o aperture fixo em f/1.4 eu comecei tentando aumentar o máximo do shutter speed pra mais de 500 pra ver se a imagem fica mais nítida. Mas aumentando o shutter speed tem menos tempo pra luz entrar e por isso eu era obrigado a aumentar meu ISO pra 1600. Só que eu já falei que aumentar o ISO aumenta a sujeira na imagem. O ideal é o ISO estar em 100. E a única configuração que chegou nisso foi shutter speed em 30 e isso mantendo a ilumintaçao da Godox em 100%. Eu preferia conseguir manter a luz da Godox em 50% ou menos, talvez aumentar o ISO pra uns 200 ou 400 e aumentar o shutter speed pra 125 a 250.

Só que nenhum dos testes que eu fiz com todas as combinações dessas variáveis me deixou muito contente. Então até eu testar mais vezes, por enquanto vou manter o Godox em 100%, o aperture em f/1.4, o ISO fixo em 100, e o shutter speed fixo em 30. Aqui é a hora que se alguém que tem mais experiência quiser mandar sugestões, não deixe de comentar abaixo.

Eu sempre quis meu fundo mais escuro mas isso significa forçar mais a câmera. A idéia da nova luz foi ter um difusor direcionado mais em cima de mim e com mais força mesmo colocando um grid na frente, por isso a Godox. Uma desvantagem que eu não sabia é que o Godox tem uma ventoinha barulhenta pra caramba e por isso eu preciso aumentar o filtro de redução de barulho em pós-produção, no Premiere. No futuro um upgrade seria trocar a ventoinha dele pra uma da Noctua por exemplo, que é mil vezes mais silenciosa. Aumentando o filtro de som em pós provavelmente a qualidade do som pode cair um pouco, espero que não dê pra notar.

Finalmente, vamos ao terceiro e último upgrade, e a segunda coisa mais importante depois da luz: o som. Muito antes de começar o canal, quando eu só tinha idéias de um dia gravar alguma coisa, eu já sabia que o som seria fundamental e eu também sabia que o som gravado num mísero celular ou mesmo em qualquer câmera como minha Canon Rebel seriam medíocres. No mínimo eu precisaria ligar um microfone com cabo ou wireless direto na câmera e deixar esse microfone o mais perto possível da minha boca. Mesmo gravando num ambiente fechado, sem barulho ambiente, como vento, e com pouco eco, só a distância de mim até a câmera já é suficiente pra garantir um som porcaria.

Por isso desde o primeiro episódio eu gravo o som separado num gravador com um microfone de lapela da Sony. Eu mostrei no episódio de um ano que eu uso este gravador da Tascan DR-05. Não chega a ser um problema mas é um inconveniente ter que gravar separado porque eu continuo gravando o som na câmera pra servir de referência pra sincronizar com o áudio do gravador e eu preciso fazer essa sincronia em pós-produção, na edição. É um passo extra que não consome tanto tempo porque a sincronização do Premiere é bem rápida, mas é um passo extra e mais uma variável pra causar problemas. Por exemplo, mais de uma vez eu tive problemas em esquecer de ligar o gravador numa pausa. Lembra que a câmera tem um tempo limite de 30 minutos? Então, a cada 30 minutos eu preciso religar a gravação do video e do áudio, e adivinha se eu já não esqueci de religar o áudio. Quando isso acontece eu tenho que falar tudo de novo. Quando eu vejo que não está gravado eu saio assim (XINGANDO)

A outra opção é um microfone wireless ligado direto na câmera e sendo gravado junto com o vídeo. Existem boas opções pra isso como o Senheiser 112P com microfones Lavalier pra ligar na câmera. Mas, como tudo até agora, essa opção é tudo menos barato. Iria me custar uns 600 dólares. Outra marca muito reconhecida é a Rode, muita gente usa o Boom Kit deles. Se você não sabe o que é um microfone boom é aqueles que fica em cima da gente usando um tripé girafa e que a gente coloca numa posição em cima da cabeça e que não aparece no vídeo.

Eu pensei em usar um boom kit mas eu fiquei com medo dele gravar eco junto e eu não estava muito a fim de encapar todas as minhas paredes com placas acústicas pra cortar eco. Sem contar que agora essa Godox faz um barulho enorme e ia ficar bem do lado do Boom. A Rode também tem outro produto que muito YouTuber usa, o RodeLink Wireless Filmmaker Kit, que é um microfone sem fio que eu posso colocar perto de mim e outro componente que liga na entrada de áudio da câmera. O problema é que esse kit não sai por menos de 400 dólares, não muito longe da opção da Senheiser.

Mas recentemente a Rode lançou um novo produto, o Rode Wireless Go, que é este conjunto de dispositivos. Essa parte vai ligada em cima da câmera e esta outra parte fica junto de mim. É um conjunto sem fio. Claro, a qualidade técnica é menor do que o Filmmaker Kit ou a Senheiser mas ela custa só 200 dólares. E eu comprei também este microfone de lapela Rode smartLav+ Omnidirecional Lavalier de 65 dólares com o adaptador 3.5 milímetros Rode SC3 de 15 dólares.

Ou seja, uma solução de 280 dólares versus uma solução de potenciais 600 dólares por uma qualidade razoavelmente similar pra substituir meu gravador Tascan de 100 dólares. Andei fazendo alguns testes e eu até acho que o som da Tascan é um pouco melhor e mais encorpado, e isso faz sentido considerando que é um equipamento dedicado só pra processar e gravar áudio. Mas considerando o passo extra que estou cortando na produção, é menos trabalho e menos possibilidades de eu errar, e isso já compensa.

Pronto, agora sim, som novo, luz nova, lentes novas, e é esta a nova qualidade técnica do canal pelos próximos meses. Em resumo então o upgrade inclui o pack de baterias pra câmera, o canhão de luz Godox SL 60w com o Light Dome Mini II da Aputure, o tripé girafa que eu comprei aqui no Brasil mesmo porque era grande demais pra trazer na mala, o Rode Wireless Go com o microfone de lapela Lavalier. O que vocês acharam? Fez alguma diferença esse novo estilo visual?

Eu também ia trocar o teleprompter por um Glide Gear TMP-500 mas esse foi o único item que a Amazon errou e me mandaram outra coisa nada a ver. E aqui vai uma diferença grande da logística nos Estados Unidos. Na mesma hora que eu recebi e vi que estava errado, entrei no site e cliquei em Refund. A UPS vai buscar no hotel onde eu estava, eu não preciso fazer mais nada, e em alguns dias o valor retorna pro meu cartão de crédito. Posso estar errado, mas se fosse no Brasil, eu ia precisar ir pessoalmente nos Correios pra mandar de volta, o que seria um puta saco. Pra qualquer e-commerce ir pro próximo nível a gente precisa de uma UPS que vem até aqui em casa buscar, não faz nenhum sentido eu ter que me deslocar até uma agência de correios pra devolver um ítem que foi entregue errado. Ter que ir até uma agência é muito anos 80.

De qualquer forma, fiquei contente com essa nova configuração. Ah sim, e por último uma coisa que esqueci mesmo de mencionar no episódio de bastidores de um ano, eu acabei de explicar sobre difusão da luz, mas não é só isso. Pra ajudar eu também uso outro truque que é este pó branco na cara. Isso é outro componente que ajuda a suavizar os defeitos dessa cara feia. Some isso à luz soft e o efeito é que parece que você tem pele boa, na verdade é tudo truque.

Enfim, tudo que falei hoje é aspecto técnico. Equipamentos não fazem um canal, é o conteúdo que faz e a forma de apresentar esse conteúdo. Não importa quantos milhares de dólares de equipamento você gaste se essa parte não está resolvida primeiro. Da mesma forma um Macbook Pro caro Não vai te tornar um bom programador. Um Porsche não vai te tornar um bom motorista. O máximo que um bom equipamento pode fazer é ajudar a ir um degrau mais longe se você já subiu os mil degraus anteriores da prática e do conteúdo. Eu só comprei esse equipamento todo depois de mais de um ano fazendo videos praticamente toda semana. E eu só comecei o canal quando eu acumulei mais de 20 anos de conteúdo e quase uma década de prática falando em público pra ter certeza que eu ia ter coisas úteis pra dizer que não é só copy e paste do que se acha facilmente googlando um pouco.

Como eu disse no começo, eu não sou nem fotógrafo e nem videógrafo profissional, eu ainda sou nível junior aqui e mesmo depois de muitas horas recombinando e reconfigurando tudo, ainda não está 100%, então se você tem dicas de como eu posso usar melhor ainda esses equipamentos novos, não deixe de mandar nos comentários abaixo, se curtiram o vídeo deixem um joinha, compartilhem com seus amigos pra ajudar. E não deixem de assinar o canal e clicar no sininho pra não perder os próximos episódio. Provavelmente semana que vem de novo não vai ter vídeo porque vou viajar de novo. Então mais um motivo pra clicar no sininho pra serem notificados quando eu soltar os próximos videos. A gente se vê, até mais!


[Akitando] #60 - Entendendo WSL 2 | E uma curta história sobre Windows NT

(obs: este script depende muito dos demos no video em si, pode ser difícil de ler só o texto)

DESCRIPTION

Eu vinha prometendo esse tema já faz algum tempo e finalmente resolvi explicar esse tal de WSL ou Windows Subsystem for Linux, a solução que permite rodar programas de Linux diretamente em cima do Windows.

Mas desde que eu comecei a acompanhar esse projeto em 2016 muita coisa mudou. O WSL evoluiu do então chamado "Bash on Ubuntu" para "WSL 1" e agora migrando pra "WSL 2".

Quais as diferenças? Como funciona? Como instalo e configuro na minha máquina?

Além de responder essas perguntas eu achei adequado aproveitar o gancho pra contar a história do UNIX/Linux sobre o Windows e como essa história remete até o anos 80! Então vamos entender a história de como a Microsoft sempre flertou e de fato dormiu na cama do UNIX muito antes do que você pensa!

Se já sabe de tudo isso e só quer saber sobre o WSL especificamente, pode pular direto pro tempo 32:12.

Errata:

Eu disse que o VHD é limitado em 256GB e não havia como redimensionar, mas na verdade tem sim. Este link tem mais informações: https://docs.microsoft.com/en-us/windows/wsl/wsl2-ux-changes

Eu disse que 8086/8088 eram processadores de 8-bits, mas esses já eram 16-bits! Sempre confundi isso.

Pré-requisitos:

  • Playlist: Começando aos 40 (https://www.youtube.com/playlist?list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki)
  • Concorrência e Paralelismo Parte 1 (https://www.youtube.com/watch?v=cx1ULv4wYxM)
  • Concorrência e Paralelismo Parte 2 (https://www.youtube.com/watch?v=gYJSWs-gp1g)
  • Gerenciamento de Memória Parte 1 (https://www.youtube.com/watch?v=9AK_1gqEfkQ)
  • Gerenciamento de Memória Parte 2 (https://www.youtube.com/watch?v=DGU1awKrNiA)
  • Virtualização Parte 1 (https://www.youtube.com/watch?v=bwO8EZf0gLI)
  • Virtualização Parte 2 (https://www.youtube.com/watch?v=mcwnQVAn0pw)
  • Apple, GPL e compiladores (https://www.youtube.com/watch?v=suSvMnNwV-8)

Links:

  • Announcing WSL 2 (https://devblogs.microsoft.com/commandline/announcing-wsl-2/)
  • Awesome-WSL (https://github.com/sirredbeard/Awesome-WSL)
  • Xfce4 Desktop Environment and X Server for Ubuntu on WSL 2 (https://autoize.com/xfce4-desktop-environment-and-x-server-for-ubuntu-on-wsl-2/)
  • HOWTO: Enable WSL2 and Convert Existing Pengwin Installations (https://www.pengwin.dev/blog/2019/6/12/enable-wsl2-and-convert-existing-pengwin-installations)
  • Plan 9 rides again; WSL file access (https://nelsonslog.wordpress.com/2019/02/16/plan-9-rides-again-wsl-file-access/)
  • WSL Distro Launcher Reference Implementation (https://github.com/Microsoft/WSL-DistroLauncher)
  • Shipping a Linux Kernel with Windows (https://devblogs.microsoft.com/commandline/shipping-a-linux-kernel-with-windows/)
  • Awesome Powershell (https://github.com/janikvonrotz/awesome-powershell)
  • WSL2-Linux-Kernel (https://github.com/microsoft/WSL2-Linux-Kernel)
  • ArchWSL (https://github.com/yuk7/ArchWSL)
  • XMing (https://sourceforge.net/projects/xming/)
  • VcXSrv (https://sourceforge.net/projects/vcxsrv/)
  • Project Drawbridge (https://www.microsoft.com/en-us/research/project/drawbridge/)
  • How to install Flat-Remix Theme on Any Linux Distribution? (https://www.osradar.com/install-flat-remix-theme-ubuntu/)
  • 2016 - Bash on Ubuntu on Windows (https://devblogs.microsoft.com/commandline/bash-on-ubuntu-on-windows-download-now-3/)
  • Installing PowerShell Core on Linux (https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-6)
  • Install .NET Core SDK on Linux Ubuntu 18.04 (https://dotnet.microsoft.com/download/linux-package-manager/ubuntu18-04/sdk-current)
  • coLinux (não-suportado mais) (http://www.colinux.org)
  • Cygwin (https://cygwin.com/install.html)

  • AkitaOnRails: Windows Subsystem for Linux is good, but not enough yet (https://www.akitaonrails.com/2017/09/20/windows-subsystem-for-linux-is-good-but-not-enough-yet)

  • AkitaOnRails: Running Arch Linux over Windows 10! (https://www.akitaonrails.com/2018/04/29/running-arch-linux-over-windows-10)

SCRIPT

Olá pessoal, Fabio Akita

O próximo grande upgrade de Windows 10 está próximo, não sei ainda se vai ser agora já em setembro ou outubro, mas quando vier ele vai trazer a funcionalidade que todo desenvolvedor estava precisando: conseguir rodar aplicações de Linux quase nativamente em Windows 10.

Acho que a esta altura quase todo mundo já ouviu falar do tal WSL ou Windows Subsystem for Linux. Porém muita gente ainda não entendeu direito o que é isso. Na real é super simples. E se você é iniciante e já assistiu meus vídeos da série Começando aos 40, muito do que eu expliquei na parte de virtualização e containers vai ser usado hoje então, se ainda não assistiu, recomendo assistir antes, especialmente os que explicam sobre virtualização e containers.

Rodar um ambiente Unix ou Linux em cima do Windows, com suporte oficial da Microsoft, é um elo perdido que muitos de nós viemos perseguindo nas últimas décadas, eu mesmo venho acompanhando isso faz algum tempo. E pra sua surpresa essa nem é a primeira vez que a Microsoft oferece algo assim. Se você está interessado só na parte prática do WSL pode pular direto pra este tempo aqui embaixo, mas como sempre eu vou fazer uma tangente pela história, então vamos lá.

Essa história se inicia nos primórdios dos anos 80. Se você não sabe disso a Microsoft cresceu graças à IBM que licenciou seu MS-DOS para ser instalado nos IBM PC, fruto da amizade da mãe de Bill, Mary Gates com o CEO da IBM na época, John Opel. Todo mundo relaciona Microsoft com MS-DOS e depois Windows. Todo mundo relaciona interface gráfica com o Macintosh original ou no máximo com o Apple Lisa e a Xerox Parc. Em termos de interfaces gráficas, existiram diversas tentativas como o GEM dos Atari ST, o famoso Amiga Workbench no AmigaOS, o DeskMate do TRS-80.

Mas em processadores de 8-bits que, com muito esforço, dava pra mapear 64 kilobytes de RAM, desperdiçar espaço com interfaces gráficas era demais. O AmigaOS, por exemplo, precisava do processador Motorola 68000 de 16 ou 32-bits. De qualquer forma pouca gente relaciona que a Microsoft teve outros sistemas operacionais e outras interfaces gráficas muito antes do Windows.

Por exemplo, vocês sabiam que a Microsoft já teve um UNIX de verdade? Sim, muito antes da Apple comprar a Next e transformar o NextStep no OS X. Aliás, vale relembrar: Linux não é um UNIX, ele é mais ou menos compatível, mas na prática é um sistema operacional totalmente diferente. O UNIX original vem da Bell Labs e sua herança hoje vive nos derivados de BSD como FreeBSD ou NetBSD e no Darwin do MacOS. A maioria dos UNIX de verdade vieram desaparecendo ou diminuindo consideravelmente com o tempo como o Solaris da Sun ou o Irix da Silicon Graphics que, de curiosidade, se você assistiu Jurassic Park, já viu o Irix funcionando.

Em 1980 a Microsoft se uniu à SCO ou Santa Cruz Operation e eles desenvolveram o Xenix que era outro UNIX de verdade, e estamos falando mais de uma década antes do surgimento do Linux. Segundo a Microsoft era um UNIX muito próximo do UNIX original versão 7 que rodava nos minicomputadores PDP-11. Aliás, de curiosidade também, antigamente a gente chamava os mainframes de computadores, coisas como o PDP são os minicomputadores, e os Commodore, TRS-80, Sinclair, MSX, e mesmo os IBM PC eram os microcomputadores. Por isso falamos em "micros".

Enfim, uma das idéias era evoluir o MS-DOS 2 pra se aproximar do XENIX em single-user, que iria virar o XEDOS, mas foi mais um vaporware que nunca saiu do papel. Aliás, foi daí que o termo "vaporware" nasceu, em 1982 por causa do XENIX. A SCO seguiu sozinha com seu SCO UNIX depois e a Microsoft desistiu da idéia e se uniu à IBM pro projeto OS/2 em 1987.

Esse período foi especialmente conturbado porque estamos falando da transição dos processadores de 8-bits como os antigos Z80 ou Intel 8086 e 8088 e indo pra 16-bits com o 80286, que inclusive foi onde eu ganhei meu primeiro computador, um clone de IBM PC XT em 1989. Porém o 286 era um processador complicado, pra dizer o mínimo. Estávamos migrando do modo real de execução pro modo protegido, que hoje é o padrão. Nessa transição apareceu a necessidade de emular o 8086 no modo protegido. Mas o modo protegido e emulado do 8086 nos 286 era falho. Além disso existia o problema do endereçamento segmentado de memória, diferente do modelo flat que você está acostumado hoje, onde pode endereçar toda a memória de 32 ou 64-bits.

Pense que em 16-bits você só consegue endereçar 64kb de RAM, pra ter acesso a mais RAM você precisa apontar pra espaços secundários, ou segmentados, de RAM. Como um índice apontando pra uma página em outro livro. Em termos simples você tinha um índice de 24-bits com endereços de 16-bits em múltiplos segmentos. No frigir dos ovos, com os problemas de hardware não seria possível rodar múltiplos ambientes paralelos de MS-DOS como deveriam. E a Microsoft obviamente não gostou muito disso.

Na parceria da IBM com a Microsoft eles desenvolveram o OS/2. A primeira versão era só modo texto, e a primeira interface gráfica tinha a cara do Windows 2, que saiu mais ou menos na mesma época. Porém a IBM insistiu no suporte ao 286 e esse foi um dos motivos da quebra da parceria porque o MS-DOS e o Windows tinham problemas com o 286. A Microsoft queria ir direto pro 386 de 32-bits que era muito melhor. Aliás, no caso do 286, imagine um processador que tinha máximo de 16MB de RAM. Claro, estamos falando de 1988. Os 386, em comparação, sendo de 32-bits, tinha máximo teórico de infinitos 4GB de RAM. Mas mais importante, rodava DOS em modo protegido perfeitamente.

Por outro lado, a IBM queria privilegiar a sua linha de hardware, claro, e eles tinham se comprometido com os 286 e sua linha de PCs PS/2 (entenderam? PS/2, OS/2 ...) e por isso insistiram no OS/2 ser especializado nesse processador. Então em 1992 a IBM e a Microsoft se separaram, a IBM se manteve com o OS/2 e a Microsoft estava muito bem, obrigado, com seu Windows 3, a interface gráfica de maior sucesso da época.

Em paralelo a isso a Microsoft começou a criar um sistema operacional portável pra múltiplas arquiteturas de computador como processadores RISC da MIPS, PowerPC, Itanium, Intel e contratou Dave Cutler que trabalhava na VMS, que fazia o VAX/VMS mas não estava interessado em muitas de suas idéias. Daí ele foi pra Microsoft e caiu no projeto NT onde ele pôde aplicar muitas dessas idéias. Aliás, o nome Windows NT tem alguma controvérsia no seu significado, muitos talvez ainda pensem em NT como sendo New Technology.

O Dave Cutler vai dizer que WNT seria uma brincadeira em cima do nome da VMS de onde ele veio. Assim como muita gente gosta de brincar que IBM é uma brincadeira em cima do nome HAL, a inteligência artificial do filme 2001 do Stanley Kubrick. Se você pegar HAL e dar shift de uma letra pra frente, o H vira i, o a vira b e o l vira m. Mesma coisa com VMS se você der shift uma letra pra frente o v vira w o M vira N e o S vira T e daí WNT ou Windows NT. Tem outras teorias mas eu pessoalmente gosto mais dessa. Hoje em dia na real não significa nada, só um nome mesmo.

Com o objetivo de ser portável o Windows NT implementa uma camada de abstração do hardware, chamado convenientemente de HAL também, ou Hardware Abstration Layer. Vocês veem que nós de programação não somos exatamente muito criativos com nomenclatura das coisas. Além disso o NT começou tentando implementar um micro-kernel, inspirado nas idéias da kernel Mach de Carnegie Mellon. Hoje em dia nós usamos kernels monolíticos ou kernels híbridos. A idéia de um microkernel é ser um kernel o mais minimalista, leve e estável possível rodando no modo de supervisão ou Ring-0 que é o anel de maior privilégio. Eu expliquei isso em outro vídeo. Daí todos os serviços que compõe o sistema rodam em user mode ou Ring-3. O problema disso é que você acaba com um overhead muito grande de IPC ou interprocess communication pois os programas rodando no anel de menor privilégio precisam ficar constantemente se comunicando com o kernel que roda no anel de maior privilégio.

Essa controvérsia de micro-kernels versus kernels monolíticos ou macro-kernels foi tema de grande discussão na comunidade Linux no começo, inclusive é um episódio histórico a discussão de Linus Torvalds defendendo o design monolítico da kernel do Linux contra a visão acadêmica de micro-kernels do professor Andrew Tanenbaum, autor de um dos livros sobre sistemas operacionais mais famosos e que você provavelmente teve que estudar se cursou ciências da computação. Na prática, apesar do design de micro-kernels ser o mais elegante, macro-kernels funcionam melhor como o sucesso do Linux já comprovou.

No caso do NT, por exemplo, subsistemas de drivers de I/O, como video ou mesmo impressão rodavam tudo em Ring-3, em user-mode, como deveriam. Mas no Windows NT 4 eles foram movidos pro Ring-0, dentro do espaço do kernel. Eu costumo dizer que o Windows NT mais estável e robusto era o Windows NT 3.5 justamente porque ele tinha o menor kernel, mas pro hardware da época acho que não tinha muita alternativa. No Windows 2000 ou XP se não me engano eles moveram uma parte do IIS na versão 6.0, pro Ring-0 na forma do driver HTTP.sys. Isso substituiu o Winsock que rodava em user-mode, e eu acho que isso foi um erro. Imagine parte do seu servidor web embutido na kernel incluindo coisas como cache e fila de requests. De qualquer forma isso garantiu melhor performance pro IIS se comparado ao Apache que rodava exclusivamente em user mode, mas ao custo de bugs na stack de HTTP potencialmente causarem problemas no nível do kernel, que poderia levar desde um crash do sistema até a buracos sérios de segurança.

A equipe do Windows NT era formada por muita gente da DEC ou Digital Equipment Corporation, liderada pelo Cutler, e uma das tais idéias que ele trouxe da VMS foi a noção de um sistema operacional orientado a objetos e por isso na arquitetura do NT você tem um Object Manager na camada privilegiada do Windows Executive, abstraindo todos os recursos do sistema na forma de objetos lógicos. Além deles alguns membros da equipe vieram do time do OS/2 que também queria integrar noções de orientação a objetos. Uma das coisas que eu achava fascinante na interface do OS/2, o Workplace Shell, é que tudo eram objetos. Infelizmente isso não foi implementado no Windows. Mas por exemplo, em vez de abrir um programa e clicar no menu em Novo Arquivo, você abria uma pasta de templates e arrastava o template pro desktop pra criar um novo arquivo, como uma instância de uma classe. A idéia de orientação a objetos foi embutido em todos os sistemas operacionais dos anos 90 e por isso coisas como a API de um NextStep sendo orientados a objetos, e você tem abstrações e encapsulamento de comportamento criando coisas como o Hardware Abstration Layer do NT.

Pra todos os efeitos e propósitos, do fim dos anos 80 pro início dos anos 90 eu diria que os sistemas operacionais desktop mais avançados eram o OS/2, o AmigaOS, o NextStep e o Solaris. Eles inspiraram diversos outros como o BeOS ou o Irix. Mas os mais populares eram sem dúvida o MS-DOS, o Windows 3.1 e o System 7. O Windows NT apareceu pela primeira vez em 1993 com o nome de Windows NT 3.1 pra ser paralelo ao nome Windows 3.1 que era completamente diferente e ainda rodava por cima do DOS. De comum eles tinham basicamente a mesma interface gráfica.

Daí em 1995 tudo mudou com o advento do Windows 95, trazendo a era de 32-bits pros desktops populares. Comparado com hoje em dia, eu lembro como a gente achava o Windows 95 pesado, precisando de 4MB de RAM sendo que o Windows 3.1 rodava tranquilamente com 2MB de RAM. No ano seguinte saiu o Windows NT 4.0 com a interface mais parecida com do Windows 95. Os NT, pela natureza da sua arquitetura mais robusta também precisava de mais recursos pra rodar, eu acho que com menos de 8MB ou 16MB de RAM não dava pra rodar decentemente, e por isso eles eram mais usados em servidores.

Nos anos 90 fomos evoluindo do Windows 95 pro 98 até o famigerado Windows ME ou Millenium Edition. E no lado do NT que era mais voltado a servidor fomos do NT 4 pro 2000. Passamos os anos 90 surfando na Lei de Moore e processadores mais rápidos iam saindo o tempo todo, do 486 pros Pentium pro Pentium II, Pentium III, no servidor tínhamos os Itanium. RAM foi ficando mais barato. A grande virada veio em 2001 com a unificação dos Windows no famigerado XP e finalmente nos livramos do legado do MS-DOS por baixo do Windows. Desde então sempre tivemos uma versão de NT pra desktop e outro pra servidor.

Então na era do XP tivemos o Windows Server 2003 e 2003 R2. Com o Windows Vista sucedendo o XP tivemos o Windows Server 2008. Depois o Windows 7 e o Windows 2008 R2. Depois disso o Windows 8 e o Windows Server 2012 e 2012 R2 e na geração do Windows 10 a partir de 2015, que teoricamente vai ter só upgrades sem mudar o nome, tivemos o Windows Server 2016 e Server 2019. O Windows 10 hoje está na oitava edição estável build 1903 de maio de 2019. Essas edições costumam sair entre abril e maio e depois outubro, por isso eu acho que a próxima edição importante vai sair em outubro de 2019.

Eu falei que o NT desde o começo veio com a mentalidade de ser portável, ele não rodava só em Intel. Mas aos poucos o suporte de hardware foi diminuindo à medida que essas arquiteturas foram caindo em desuso. Por exemplo, no Windows 2000 caiu o suporte a MIPS, Alpha e PowerPC. Mas além do lado hardware o NT implementou a idéia de "personalidades" ou subsistemas. Você tinha a Win32 que é o suporte a API moderna do Windows. Hoje em dia programas 32-bits rodam emulado sobre o WoW64 ou Windows 32 on Windows 64. Mas desde o começo ele foi lançado com suporte a subsistemas como OS/2 que permitia um grau de compatibilidade com a API do OS/2, já que a Microsoft tinha interesse no nicho que eles ajudaram a criar com a IBM.

Claro que eles precisavam manter compatibilidade com o MS-DOS e pra isso eles já embutiam uma virtual machine pra DOS. Sim, um tipo de máquina virtual. E pra conseguir atender contratos de governo que requeriam UNIX eles também tinham um subsistema compatível com POSIX, que como eu já expliquei em outro episódio, é a superfície de compatibilidade com os UNIX da época.

Ou seja, seria possível pegar o código fonte de aplicativos feitos pra UNIX e teoricamente compilar num Windows NT e rodar nativamente! Ele era compatível com o padrão POSIX.1 por isso não tinha um shell ou ambiente de uso e comandos de usuários, coisas como um mísero 'ls', que só passaria a existir como padrão no POSIX.2. Por isso pra usuários normais de Linux como nós, esse ambiente é basicamente inútil. Só serve se você tinha código fonte em C compatível com POSIX.1 e precisava de um compilador C compatível.

Com a evolução rápida das distros Linux nos anos 90 e igualmente rápido desuso dos UNIX, esse subsistema no Windows era meio inútil. Nesse vácuo, projetos open source como o Cygwin apareceram. Cygwin se chama assim porque foi criado pela empresa Cygnus Solutions que depois foi adquirida pela RedHat. Era um ambiente em user-mode compatível com POSIX e com Linux em particular. E diferente do subsistema POSIX original da Microsoft, eles portaram boa parte do toolchain do GNU no pacote, trazendo shells como bash e além das ferramentas pro compilador, também trouxe diversas outras coisas úteis como awk, sed, tar, ssh, servidores como Apache, Postgres, linguagens como Perl, Python, Ruby, Prolog e muito mais.

Era como uma distro baseada em Linux rodando sobre uma camada que tentava abstrair o NT por baixo na forma de DLLs. Muita gente ainda usa Cygwin até hoje e sempre foi a melhor ou mesmo a única forma de rodar muitas ferramentas de Linux de forma nativa no Windows. Mas Cygwin não se encaixa na categoria de distro de Linux porque ele tem uma semi compatibilidade em nível de código fonte, mas os binários de Linux não rodam no Cygwin nativamente, precisa recompilar sempre.

Com o passar dos anos o subsistema POSIX original foi removido no Windows XP e Windows Server 2003 sendo substituído por outro subsistema que foi originalmente desenvolvido por uma empresa chamada Interix, adquirida pela Microsoft, e sua solução foi renomeada como Windows Services for UNIX ou SFU e você podia instalar opcionalmente. O suporte ao SFU veio gradativamente diminuindo, tendo muitos de seus componentes removidos do instalador até o Windows 8 e Windows Server 2012. Mas pra todos os efeitos e propósitos ele é muito similar ao Cygwin só que o Cygwin é compatível com GNU/Linux e o SFU é compatível com UNIX. Novamente, se você tinha código fonte de UNIX podia compilar no SFU e rodar no Windows.

Em 2004 surgiu a primeira forma de rodar Linux sobre o Windows de verdade, sem virtualização. Foi o Cooperative Linux ou coLinux. Infelizmente a última versão estável dele é de 2011 então não espere que ele funcione hoje em dia. Mas em vez de ser um subsistema ou uma máquina virtual, ele era uma kernel linux modificada que era carregado no Ring-0 do lado do kernel do NT. E ele compartilhava os recursos da máquina com o kernel NT, por isso cooperativo. Isso era possível através de um driver, e drivers rodam em Ring-0 junto com ferramentas como um Windows Service pra dar acesso. Daí em user-land você podia rodar os binários originais de uma distro como Ubuntu ou Arch. Dava pra acessar o terminal via SSH ou mesmo a interface gráfica do X via um VNC. Pra todos os efeitos era quase como rodar um Virtualbox mas sem o overhead de virtualização. Eu imagino que manter suporte de algo assim deve ser bem complicado porque você meio que está lobotomizando o kernel do NT por um hack via um driver.

Como podem ver, rodar Linux ou UNIX sobre o Windows é uma coisa que muita gente já tentou fazer de diversas formas. Com hardware moderno, a opção mais estável é abrir um Virtualbox ou VMWare e rodar virtualizado. Graças à evolução do suporte de virtualização dos processadores, na minha experiência não só é viável mas serve muito bem como máquina principal de desenvolvimento. E quando eu digo hardware moderno estou falando no mínimo de um Core i5 de 4 cores com no mínimo 8GB de RAM, preferencialmente 16GB. O problema de virtualização normal é que normalmente você precisa pré-reservar quantos cores e quanto de RAM vai ser dedicado pra máquina virtual.

Vejam o problema. Com soluções como o subsistema POSIX, o Cygwin ou o SFU tudo roda nativo, não é virtualização. Mas pra funcionar você precisa ter o código fonte de tudo, talvez modificar alguma coisa, compilar e gerar um binário nativo de Windows NT. Se conseguir fazer isso o binário vai rodar como qualquer outro binário de Windows. Por outro lado, é muito difícil conseguir compatibilidade com tudo e muita coisa simplesmente não vai nem compilar sem modificações significativas. Pelo simples fato que as primitivas do kernel do NT são diferentes do kernel do Linux. Já expliquei em detalhes em outros vídeos como o Windows, o Mac e o Linux gerenciam coisas como estrutura de processos, scheduler de threads, memória, I/O de formas diferentes.

Por exemplo, num SFU não adianta eu compilar alguma coisa como o programa 'ip' ou o antigo 'ifconfig', eles vão tentar mexer em coisas como o /etc/network/interfaces que o Windows NT não usa pra expor suas interfaces de rede. Isso sem contar que coisas como 'fork' funcionam diferente no Windows, como já expliquei no vídeo de gerenciamento de memória. Não adianta eu compilar um programa que gerencia processos via o diretório /proc porque o Windows não expõe os metadados dos processos lá. O sistema de autenticação e permissão são completamente diferentes, então nenhum programa que gerencia usuários e permissões como 'useradd' ou 'chown' vai conseguir modificar nada no Windows. Essas diferenças tornam complicado rodar programas de UNIX ou Linux lado a lado de programas Windows porque existe uma dissociação semântica enorme.

Por outro lado, rodar dentro de uma virtual machine traz a vantagem de isolar completamente os comportamentos de Linux e Windows. De dentro da máquina virtual tudo funciona perfeitamente como se fosse uma máquina de verdade isolada. Daí você controla coisas do host Windows via ferramentas de Windows e coisas de Linux separadamente com coisas de Linux e cada um vive feliz separado.

Vamos ver o caso do macOS rapidamente. Como ele foi construído sobre uma fundação UNIX, todo o sistema operacional obedece às leis do UNIX. Todo programa de Mac é de fato um processo UNIX e pode ser controlado com coisas que todo mundo de Linux conhece. Se eu usar o comando ps no terminal, vai listar processos como o Chrome ou Final Cut. Se eu usar o comando kill no terminal, vai matar de fato os programas todos. Se eu usar comandos como ip eu de fato gerencio as interfaces de rede da máquina, e assim por diante. A API do Mac é construído sobre as APIs do UNIX por baixo. Então a integração é praticamente 100%. Um serviço de UNIX é um serviço de Mac e assim coisas como servidor de SSH ou Postgres rodam lado a lado de serviços do Mac. Usuário de Mac são usuários do UNIX. Permissões de arquivos feitos via o Finder ou feitos via Terminal com comandos como 'chown' ou 'chmod' são a mesma coisa. Por isso existe coerência semântica entre os aplicativos gráficos e os de linha de comando. E a Apple trouxe muito do toolchain GNU pra dentro, então o MacOS é como se fosse um FreeBSD mais bonito, na prática.

Por outro lado, assim como no subsistema POSIX, Cygwin ou SFU no Windows, o macOS tem compatibilidade de código-fonte mas não de binário. Binários formato ELF de Linux não são compatíveis com binários de BSD ou macOS ou Cygwin ou SFU. Por isso no Mac você precisa compilar programas de Linux especificamente pra rodar no Mac e por isso a gente não pode usar pacotes pré-compilados que já existem em distros como Ubuntu ou Arch. Por outro lado, ao contrário de Cygwin ou SFU, o comportamento do macOS, que é parecido com o comportamento do BSD e tem a mesma semântica do Linux, garante que quase todo o código-fonte seja compatível porque coisas como interfaces de I/O, gerenciamento de memória e de threads é significativamente parecido.

Então existe algum trabalho que precisa ser feito no código-fonte de alguns programas, mas no geral, basta recompilar no Mac e tudo magicamente funciona. O mesmo não pode ser dito em ambientes como o antigo subsistema POSIX ou SFU ou Cygwin. E mesmo quando compilam, eles não tem acesso ao resto do sistema Windows.

A grande reclamação do povo de Linux quanto a gerenciar os recursos de um Windows é que tudo precisa ser feito via ambiente gráfico. Por isso gerenciar um servidor remoto sempre exigiu coisas como um Remote Desktop ou VNC pra podermos ter a tela gráfica remotamente. É um puta pé no saco quando em servidores Linux podemos conectar via terminal com um cliente de SSH e gerenciar todos os recursos da máquina via linha de comando. Pra resolver parte disso a Microsoft criou o Powershell.

Em resumo, a história remete ao fim dos anos 90 de novo, quando a Microsoft iniciou o projeto .NET. Com o tempo boa parte, se não toda a API do Windows, foi mapeada em classes dos frameworks disponíveis no .NET, além de componentes COM e WMI. Porém o antigo programa de linha de comando do Windows, o Command, que herda as características do antigo MS-DOS, é um lixo. As linguagens de script que ele traz como o BATCH ou REXX, oriundo da antiga herança do OS/2, são muito fracos e quase nada do sistema operacional é exposto pra linha de comando, você é obrigado a programar coisas como scripts em visual basic pra acessar o WMI ou componentes COM que tem algum acesso a algumas coisas do sistema como gerenciar usuários. Esquece fazer scripts complicados como os que fazemos em Bash pra Linux.

Porém, uma idéia interessante é um console de linha de comando com acesso ao framework do .NET. E é basicamente isso que é o Powershell. Ele trás uma linguagem de script mais poderoso e com muitas coisas que encontramos num Bash. Ele tem inclusive um pipe! E eu argumentaria que um pipe superior ao do Linux. No Linux todo script ou comando aceita argumentos e um stream de bytes que chamamos de standard input e tem uma saída de bytes na forma do standard output, que eu também já expliquei nos vídeos sobre concorrência e paralelismo. E por isso você precisa de comandos como awk ou sed ou grep pra tratar o texto que os scripts devolvem. No caso do Powershell os comandos devolvem estruturas de .NET, ou seja, são dados estruturados.

O episódio nem é de Powershell mas obviamente agora eu empolguei. Eu também acho interessante que muita gente que usa Linux nunca viu Powershell. Pense num script de Bash pra selecionar processos que estão usando mais que 100MB de RAM, como você faria. Num bash provavelmente algo parecido com este script aqui:

ps -eo rss=,pid=,user=,comm= k -rss |
  while read size pid user comm
  do
    if [ "$size" -gt 102400 ]
    then
      echo "$pid $size $user $comm"
    else
      break
    fi
  done

Ou seja, precisamos ir linha a linha da saída do comando ps e checar o tamanho em kilobytes até achar o que queremos. Em Powershell podemos fazer assim:

Get-Process | Where-Object WorkingSet -gt 104857600

E como o Powershell suporta aliases e ele já vem com muitos aliases pré-programados, a mesma linha poderia ser escrita assim:

ps | ? WorkingSet -gt 104857600

Do ponto de vista de usabilidade pra novatos, a sintaxe do Powershell é muito mais fácil de ler. E pra veteranos de shell existem formas de simplificar pra comandos mais familiares. E veja como estamos usando o pipe pra passar os objetos do ps pro where-object fazer um filtro na propriedade do objeto. E isso é universal pra todos os comandos.

Tecnicamente eu diria que o Powershell pode ser considerado superior em muitos aspectos a Bash ou outros shells de Linux. Eu acho que não é 100% do Windows que pode ser controlado via Powershell mas uma parte considerável pode, e ele pode ser estendido via .NET, então seja via C# ou F# você pode expor o que quiser pra esse shell. E mesmo se você for de Linux pode usar Powershell, porque a Microsoft abriu não só o .NET na forma do .NET Core como open source como também o Powershell na forma do Powershell Core. Aqui o problema é o inverso, ainda não sei quanto do Powershell se integra aos recursos do Linux mas ele pode ser uma opção interessante pra fazer scripts de automação numa linguagem menos cheia de truques se comparado a um Bash, especialmente se estiver rodando coisas como SQL Server pra Linux e outros programas .NET. Ele é particularmente bom pra lidar com dados estruturados como JSON.

Essa tangente pro Powershell foi primeiro pra introduzir vocês ao tema, mas segundo pra dizer que apesar dele ser muito bem feito e eu pessoalmente achar elegante e um passo grande pra Microsoft, ele ainda não resolve o problema de quem quer usar o Bash de verdade e está acostumado com o ferramental GNU. Mesmo o Powershell sendo bacana, a gente ainda quer usar ferramentas antiquadas mas funcionais como awk, sed, grep e tudo mais. A única saída nesses casos é instalar Cygwin ou rodar num Virtualbox já que o subsistema POSIX já morreu, o SFU já morreu, e o coLinux não tem suporte desde 2011.

Até aqui eu expliquei como era possível rodar alguma coisa de UNIX ou Linux no Windows mas e o oposto? Ou seja, rodar programas de Windows no Linux? Se você já brincou de Linux por algum tempo já deve ter ouvido falar ou mesmo usado o projeto Wine ou Wine is not an Emulador que é um acrônimo recursivo, coisa de programador, como eu já disse a gente é ruim de nomenclatura. O Wine é um projeto capaz de carregar um binário feito pra Windows, sem precisar recompilar, e executar no Linux. Pra fazer isso ele mapeia as chamadas de API pra Kernel do NT em chamadas pra kernel do Linux e faz engenharia reversa de dezenas de DLLs que compõe o Windows e com isso ele consegue executar muitos aplicativos de Windows rodando com performance quase nativa em ambiente Linux. Claro, engenharia reversa significa que 100% de compatibilidade é extremamente difícil. Muitos aplicativos abrem e rodam bem, mas algumas partes deles podem precisar de APIs que não foram reimplementados ainda e crashear ou simplesmente não funcionar.

Lembram como no video sobre Ubuntu eu falo como não dava pra rodar todos os games do Windows no Linux? Muitos me lembraram depois que a Steam tem o projeto Proton que permite rodar muitos jogos, mas não todos. Você precisa consultar o site da comunidade, o ProtonDB pra ver se seu jogo roda. Na realidade o Proton usa Wine por baixo. Ele é mais um de uma família de aplicativos que usam Wine e tentam facilitar a instalação de dependências pra programas específicos, como o antigo CrossOver. Por isso o Proton consegue rodar jogos que originalmente foram feitos pra Windows, mas em Linux. O Wine é como se um fosse uma camada de Windows dentro do Linux.

O maior problema é que como os binários do Windows não são código aberto, é preciso fazer engenharia reversa dos binários e reimplementar do zero no Linux. É um processo black-box de desenvolvimento, ou mais corretamente, um grande cornojob de tentar executar o binário do Windows, ver ele crasheando, ver que chamada de sistema ele tentou fazer que ainda não existe e implementar essa função no Linux. Função a função. Wine existe já faz muitos anos e com o tempo ele evoluiu bastante, mas o Windows não parou no tempo também, então cada vez que sai uma versão nova, o Wine ganha mais trabalho pra suportar as coisas novas. É um trabalho insano que me deixa fascinado pelo fato de até hoje ainda existir.

Muito bem, vamos resumir agora. Vocês lembram o que eu já expliquei em outros vídeos. Um sistema operacional é composto de pelo menos duas partes principais, o kernel e drivers e outros subsistemas que rodam no Ring-0 de maior privilégio do sistema. E a outra parte são outros subsistemas e aplicativos que rodam em user-land ou Ring-3 de menor privilégio. Pra rodar os programas de outro sistema operacional você precisa ter as dependências, talvez fazer algumas modificações no código-fonte, recompilar o programa. É o processo que chamamos de "portar". É pra isso que serviam ambientes como o subsistema POSIX, o SFU e o Cygwin. É como funciona no macOS hoje ou mesmo nos BSDs. Compatibilidade de nível de código-fonte. Quer rodar programas de Linux fora do Linux, precisa recompilar em todos esses ambientes: Windows, macOS ou outros UNIX.

Pra rodar um binário de outro sistema operacional, sem recompilar, você precisa ter o kernel e dependências rodando lado a lado do seu sistema principal e um mecanismo pra carregar os binários não modificados. O coLinux conseguiu executar a kernel do Linux cooperativamente, compartilhando os recursos da máquina, do lado da kernel do NT, ele conseguia montar um filesystem de Linux e executar os programas de Linux em user-land sem precisar recompilar nada. Você podia rodar os binários de um Ubuntu ou Arch por cima desse ambiente.

Fora isso você tem a opção de virtualização, seja com VirtualBox, VMWare ou Hyper-V, daí você roda tudo em user-land mas com as instruções da VT-X da Intel que permite o kernel virtualizado conseguir acessar o hardware por baixo com menor overhead possível. Você perde 20% ou mais de performance virtualizando, dependendo da configuração do seu hardware, mas no geral tudo funciona perfeitamente. Virtualização é uma opção quase plug-and-play hoje em dia.

Eis que do nada a Microsoft anunciou o projeto Windows Subsystem for Linux ou WSL 1 como podemos chamar hoje. Ele começou a aparecer no Fast Ring do Windows Insider lá pelo Anniversary Edition acho que pelo começo de 2016, e foi lançado de verdade no Creators Update uns 3 meses depois. Hoje em dia temos o WSL 2 que é completamente diferente e já vou explicar porque. Mas o WSL 1 do ponto de vista técnico é muito mais interessante, mas eu entendo porque eles desistiram dessa forma.

Antes de mais nada, a Microsoft faz muita pesquisa, e eventualmente muitos dos resultados dessas pesquisas realmente acabam no Windows de verdade. Um desses projetos foi o Drawbridge que estava pesquisando formas de rodar processos isolados, como em containers, sem precisar de virtualização. Um dos resultados foi permitir o kernel de criar processos com o mínimo ou zero de interferências. Lembram nos episódios de gerenciamento de memória que eu expliquei que dentro do espaço de endereços virtuais do processo o Windows mapeia suas DLLs de sistema e outras coisas pra compartilhar seus recursos com os processos e por isso um processo de 32-bits no Windows de 32-bits nunca tinha os 4GB totais disponíveis pra ele? O resultado desse projeto Drawbridge foi permitir o kernel NT de criar o que eles chamaram de Minimal Process e Pico Process que são processos sem interferência do OS, com o espaço de memória limpo. A diferença de um processo Minimal e Pico é que no caso do Pico existe um driver associado a ele que permite a comunicação desse processo pra fazer syscalls.

Isso existe desde 2013 e o time do WSL resolveu usar esse recurso pra carregar o binário não-modificado de um programa Linux que tem o formato ELF64 pra dentro de um Pico process. Agora pra executar, esse programa vai querer fazer coisas como syscalls pra kernel do Linux. E aí entra a parte do driver. Esse driver vai converter as chamadas pra kernel do Linux em chamadas pra kernel do NT. Então na prática não existe um kernel de Linux rodando. Quando o programa de Linux chamar um fork ele vai ser traduzido pro equivalente NtCreateProcess, ou se chamar fopen ele vai ser traduzido pro NtOpenFile e assim por diante. Na realidade a tradução acontece no nível do assembly, mas a idéia é a mesma.

Como já expliquei, o Windows trata todos os recursos do sistema como objetos, incluindo arquivos e o file system, tudo é roteado pelo Object Manager. E ele sempre teve essa idéia do HAL de abstrair o hardware. E de fato, por cima do NTFS temos o VFS ou Virtual File System. Se vocês já usaram Windows e Linux sabem que existem diferenças em como o filesystem NTFS do Windows e o EXT4 do Linux se comportam. Por exemplo, no Windows não tem como deletar um arquivo que está em uso por algum programa, no Linux podemos renomear ou deletar o arquivo mesmo que ele esteja em uso. São comportamentos diferentes e não defeitos na operação, prova disso é que o WSL 1 replica o comportamento de um EXT4 por cima do NTFS removendo algumas validações e checagens que no NTFS são ligadas por padrão. Inclusive o WSL consegue replicar o comportamento do ProcFS e SysFS pra expor partes do sistema operacional como arquivos, como no diretório /proc que lista processos do sistema como expliquei no episódio de Ubuntu.

Então esse novo subsistema que seria o herdeiro do antigo subsistema POSIX e do SFU emula o comportamento da kernel do Linux, sem de fato ter o kernel do Linux e sim traduzir tudo pro kernel NT, com a vantagem de carregar binários não modificados. Como eu já expliquei antes, tirando a kernel o que muda de uma distro pra outra são os binários de user-land. E como o WSL consegue carregar os programas de Linux diretamente nos processos Pico, sem precisar modificar ou recompilar esses binários, você pode literalmente pegar todos os binários de um Ubuntu ou Arch ou Fedora e rodar em cima desse subsistema.

Uma grande vantagem dessa forma é que ao abrir um Bash e usar comandos como ps ou kill podemos não só manipular programas Linux como programas Windows também. Muitos comandos que num Cygwin seriam inúteis como programas pra configurar interfaces de rede e muito mais passam a funcionar minimamente. Programas que manipulam arquivos podem manipular alguns arquivos do lado Windows também. Foi a primeira vez que programas Linux não modificados puderam rodar lado a lado de programas Windows. Era bizarro porque eu podia abrir o Task Manager do lado do Windows e ver cada programa de Linux individualmente listado. A integração era muito bem feita embora ainda incompleta.

Eu rodei esse ambiente por algum tempo, e apesar de não ser tudo que funcionava, era possível rodar tudo que eu precisava pra desenvolver projetos de Ruby on Rails ou Node.js incluindo bancos de dados como Postgres ou Redis e carregar servidores que corretamente faziam binds nas portas nativas do Windows, e conseguir testar do Chrome do Windows. Seria esse o ambiente perfeito?

Infelizmente havia ainda vários problemas. O maior ofensor eu diria que era a virtualização do file system. Essa virtualização em cima do VFS funciona, mas o VFS é interligado com o Windows Defender e outros antivírus e antimalware. Toda operação de disco no Windows dá trigger pro Defender avaliar se não tem perigo ler o arquivo. Obviamente isso adiciona um overhead gigantesco. Some a isso operações altamente custosas de I/O como um npm install em um projeto de Node e você pode ir tomar um café enquanto espera ele terminar. Isso poderia ser resolvido se a equipe do WSL mudasse a estratégia de tentar manter o file system aberto em cima do NTFS e simplesmente montasse um disco virtual dentro de um arquivão como no formato VHD que Virtualbox ou VMWare usam. Máquinas virtuais costumam montar o filesystem do sistema Guest na forma de arquivos esparsos, ou seja, digamos que você configure um HD de 1 TB dentro do Linux virtualizado mas seu HD de verdade só tem 500GB. Claro que não cabe, mas você não precisa pré-alocar esse espaço, pode só mentir pro sistema virtualizado e só alocar o que ele realmente for usar e ir adicionando novos blocos nesse disco virtual sob demanda.

Mas o problema principal não é esse. O conceito original é mapear syscalls do Linux pra syscalls do kernel NT. Eles esbarraram no mesmo problema que a equipe do Wine tem até hoje: toda vez que sai uma atualização pro Windows, alguma coisa vai quebrar no Wine porque as syscalls e outras dependências mudaram. Então manter essa camada de compatibilidade entre as duas kernels vai ser sempre instável. Toda vez que sai uma versão nova da kernel do Linux, alguém na equipe do WSL vai precisar ajustar essa camada de tradução. Então nunca vai ser possível ter um sistema totalmente estável porque essa é a natureza de wrappers e adapters, eles são alvos móveis.

Ficar fazendo isso desde 2016 demonstrou que como prova de conceito, de fato tudo funciona, mas muitas syscalls de Linux não tem equivalente em NT e davam trabalho de manter. E toda vez que a kernel atualizava tanto do lado Linux quanto do lado NT, precisava ajustar a camada de adaptação. É uma bola de neve sem fim. Por fim eles decidiram mudar a estratégia toda e recomeçar do zero.

É uma pena, porque como eu disse, eu gostava muito dessa estratégia porque uma das vantagens era ter os recursos do Windows como processos em execução, expostos numa camada Linux onde era possível usar as ferramentas de Linux pra mexer em partes do Windows. Se eles conseguissem abstrair coisas mais complicadas como o Registry em alguma forma que pudesse ser gerenciada pelo Bash, ia começar a ficar mais útil ainda, mas aí já era pedir demais mesmo. Pra isso existem coisas como o Powershell. Se a intenção é gerenciar os recursos do Windows pela linha de comando, ainda é muito melhor, mais estável e mais completo usar o Powershell.

Em vez de tentar fazer de tudo, melhor fazer menos mas fazer direito. Já que é muito difícil emular a kernel do Linux como um adaptador em cima da kernel do NT e se simplesmente rodássemos o kernel do Linux de verdade? Seria a estratégia do coLinux, mas em vez de um hack, podemos usar o Hyper-V que nesta altura evoluiu bastante e é muito bom. O problema com programas de virtualização, como expliquei antes é que você normalmente precisa reservar quantos cores e quanta RAM vai ficar travado pra máquina virtual.

Mas durante a evolução do Windows Server pra atender os requerimentos de serviços como o Azure, eles conseguiram evoluir o Hyper-V pra criar máquinas virtuais mais leves e mais flexíveis. Existe a opção de lightweight utility VMs onde ele consegue pedir mais recursos à medida que for precisando. Daí não precisamos desperdiçar 4GB do sistema deixando reservado pra máquina virtual se logo no boot ele só precisa de 500Mb, por exemplo. Além disso existe a opção de não exigir um cold boot, ou seja, um boot do zero. Ele dá a opção de você conseguir carregar o equivalente a um dump da RAM, como no processo de hibernate. Dessa forma você consegue boots quase instantâneos, que levam segundos em vez de minutos.

E como também expliquei no video de virtualização, existe a opção de paravirtualização. Numa virtualização completa o kernel dentro da máquina virtual não sabe que está numa máquina virtual, é um Inception. Numa paravirtualização você modifica o kernel de dentro pra ele estar ciente de estar num ambiente virtualizado e conseguir um comportamento melhor cooperando com o sistema operacional do lado de fora.

Um último componente um pouco inusitado foi a inclusão de uma parte do antigo projeto Plan9, no caso o protocolo 9P que era usado pra file system em rede. Pense assim, como a partir do Windows eu poderia acessar o filesystem do Linux e vice versa se agora o Linux vai rodar de dentro de uma máquina virtual? Existem várias opções como o próprio protocolo SMB e o serviço SAMBA ou mesmo SFTP que é FTP em cima de SSH. A própria Microsoft não se manifestou do porque dessa escolha oficialmente, mas algumas teorias dizem que talvez seja porque o protocolo 9P é mais simples e mais fácil de implementar do que os disponíveis hoje.

De curiosidade o Plan9 é outro projeto da antiga Bell Labs que inventou a linguagem C e o UNIX, na realidade parte das mesmas equipes criaram o Plan9 que era um sistema operacional distribuído em rede. Com o tempo o Plan9 caiu em desuso e a Bell Labs criou o projeto Inferno que tinha como objetivo virar concorrente do então novo Java da Sun, e nas pesquisas de sistemas distribuídos tanto a Sun quanto a Bell Labs chegaram à conclusão que precisariam de uma máquina virtual pra lidar com tantas configurações de rede diferentes disponíveis na época. Vocês podem ver que a história da virtualização vem de longa data, e só nesse episódio vocês viram que nos anos 70, 80, 90 até os 2000 tinha alguma coisa de virtualização em cada solução da época. Virtualização e containerização não são tecnologias novas, são décadas e décadas de pesquisas e experimentação.

De qualquer forma, somando os lightweight utility VM do Hyper-V com a capacidade de boot rápido, mais uma kernel de Linux de verdade customizada pela Microsoft, mais a implementação do protocolo 9P pra exposição do filesystem em rede, e agora temos o Windows Subsystem for Linux versão 2 ou WSL 2. Ela nunca vai ter as mesmas possibilidades de integração com o resto do Windows como o WSL 1 tinha, mas por outro lado ela oferece 100% de compatibilidade binária com Linux, coisa que o WSL 1 nunca ia ter. E como agora o filesystem se tornou virtualizado, ele parou de ter os problemas de performance por causa do Windows Defender também. Por outro lado o WSL2 não oferece ainda opções pra configurar o filesystem virtual, se não me engano ele tem um limite máximo pra 256GB, o que é suficiente pra maioria dos cenários, mas seria bom ter futuramente a opção de expandir esse drive.

Do ponto de vista de performance, sem rodar nenhum benchmark nem nada, eu diria que é a mesma performance que eu já sentia rodando num VMWare, porém reservando menos recursos da máquina no geral. Usando todos os dias tudo vem funcionando exatamente igual funcionaria num Linux em Virtualbox ou Vmware. É um linux virtualizado. E como bônus se de dentro do WSL2 você subir um servidor, digamos um servidor de Rails que se liga na porta 3000, você pode abrir o Chrome do Windows e acessar localhost:3000 e vai abrir normalmente, porque ele faz automaticamente o NAT interno de mapeamento de portas. Isso é ao mesmo tempo bom e ruim, porque se você tiver serviços no Linux com portas que conflitam com portas no lado do Windows, vai ter problemas que podem ser difíceis de diagnosticar.

Do ponto de vista de ecossistema é um passo gigantesco porque se você acompanhou pessoalmente a história que eu contei até agora, como foi o meu caso, se no ano 2000 alguém me dissesse que um dia a Microsoft embutiria o binário da kernel do Linux dentro do Windows, eu diria que a pessoa era louca e devia se tratar. Diferente de alguma coisa como Cygwin, sendo suportado pela própria Microsoft, mais e mais pessoas podem depender dessa opção como algo estável e que vai estar disponível mesmo em futuros upgrades do Windows.

A história foi longa, mas instalar o WSL 2 é super easy, barely an inconvenience... Quando sair a versão oficial no próximo upgrade do Windows 10 você pode pular o primeiro passo, mas se ainda for setembro de 2019, você vai precisar se cadastrar no programa Windows Insider. Ah sim, só funciona no Windows 10 Pro, o Windows 10 Home não tem suporte a Hyper-V se não me engano. Uma vez cadastrado, você vai precisar habilitar o Fast Ring que vai trazer os builds mais recentes do Windows mas também tem o risco de vir binários instáveis. Habilitar o Fast Ring é concordar que você está baixando versões alfa de muita coisa e instabilidades fazem parte do contrato. Eu venho usando o Fast Ring faz meses e tirando uma ou duas vezes que tive telas azuis, no geral tudo tem funcionado sem problemas graves.

No caso até a data onde estou gravando este episódio está na build 18970. Com o Fast Ring ativado é só deixar o Windows Update baixar as últimas versões. Vai demorar bastante pra baixar e instalar a última versão, vamos esperar um pouco ... só mais um pouco .... só mais um pouco .... ah sim, em uma das vezes que fui instalar ele reclamou que eu tinha o VMWare instalado, precisei baixar a versão mais nova pra ele parar de reclamar, mas cuidado com isso: com o Hyper-V ativado, não dá pra usar Virtualbox nem VMWare até eles aderirem às APIs de virtualização da Microsoft. Enfim, uma vez atualizado daí é restartar. Agora você precisa abrir um Powershell como Administrador e digitar o seguinte comando:

Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform

Isso vai exigir um restart e pronto, agora você já tem WSL. Abra um command prompt ou powershell e garanta que está com o WSL 2 como padrão executando o comando

wsl --set-default-version 2

E pra instalar o Ubuntu você pode ir na Windows Store, procurar por Ubuntu e instalar. É gratuito. Daí você espera um pouco … configura seu usuário e senha e .... Instalado. Primeira coisa que eu sempre faço quando instalo é rodar o apt update e upgrade pra atualizar todos os pacotes. Daqui você pode seguir meu video de Ubuntu e praticamente tudo deve funcionar.

Eu digo praticamente porque o WSL é feito primariamente pra aplicativos de linha de comando como o próprio Bash, coisas como ssh, git e tudo mais. Mas sempre existe a opção de instalar um cliente X no Windows e rotear o DISPLAY do X no Linux pra ser fora dele. Se você não sabia disso, o X é outro capítulo conturbado na história do UNIX e Linux. Nós tivemos o XFree86, depois o X.org e finalmente muitos estão migrando pro Wayland.

Em termos simples, o X é feito pra aplicativos gráficos. A intenção original é que um servidor UNIX com um servidor X poderia servir múltiplos terminais burros, incluindo terminais gráficos, que seriam clientes de X. Pense em servidor web e navegador web, é basicamente a mesma coisa só que em vez de trafegar um protocolo primitivo como o HTTP ele usa um protocolo próprio do X pra enviar comandos como "desenhe um botão na tela" e quando o botão é clicado essa ação trafega pro servidor X decidir o que fazer e devolver outro comando pra redesenhar alguma parte da tela. Isso até que funciona, o que o povo de desenvolvimento não gosta é da implementação, é um código difícil de dar manutenção.

E as coisas ficaram mais difíceis depois que a Apple mostrou o que era possível fazer com um sistema de composite e aceleração via hardware como o Quartz e a interface Aqua. Como você faz pros clientes X terem os mesmos recursos? Dificulta muito que o X era um monolito, que os drivers das principais GPUs como NVIDIA é proprietário e distribuído só como binário, e as versões open source tem qualidades variadas, e no final o que acaba acontecendo é que você raramente usa o X de forma distribuída e tanto o cliente quanto o servidor ficam na mesma máquina desktop de qualquer jeito. Por causa disso o X tem dois modos de renderização acelerada, a direta onde o próprio servidor X acessa a GPU e o indireto onde ele manda os comandos de OpenGL pro cliente rodar.

No caso do WSL, ele não tem acesso direto a algumas coisas incluindo o hardware de vídeo. Portanto ele depende de um rasterizador via software. Você não tem como rodar o cliente de X dentro do WSL porém você pode rodar o cliente de X de Windows e rotear os comandos de X do WSL via rede pra fora. E vai funcionar perfeitamente bem. Existem duas opções gratuitas e open source que são o VcXSrv e o XMing e uma versão paga que eu pessoalmente acho melhor que é o X410, que eu costumo usar, mas na prática tanto faz. Se eu não estou enganado todos devem suportar o modo de aceleração indireta, basta configurar dentro do WSL uma variável de ambiente dizendo isso, o libgl_always_indirect e adicionar num bashrc pra sempre ter a variável quando se logar.

export LIBGL_ALWAYS_INDIRECT=1

Pra rotear os comandos o WSL precisa do endereço IP do host Windows. No WSL 1 ficava tudo em localhost porque pra todos os efeitos e propósitos o WSL 1 rodava lado a lado do Windows e não virtualizado. No WSL 2 como ele é uma máquina virtual, do lado de dentro ele tem também uma rede virtual e o Windows do lado de fora é mapeado pra um endereço local dentro, é um tipo de ponte de rede que traduz o roteamento de pacotes do lado de dentro pro lado de fora. Se fosse localhost bastava fazer a variável Display ser igual a dois pontos e zero. Porém toda vez que a máquina reinicia pode ser que esse endereço virtual mude. Precisamos configurar uma variável de ambiente que aponte pra esse endereço e pra isso podemos fazer o seguinte one-liner, adicionar no bashrc e agora configurar a variável display pra usar o valor

export WSL_HOST=$(tail -1 /etc/resolv.conf | cut -d ‘ ‘ -f2)
export DISPLAY=$WSL_HOST:0

Colocando essas variáveis de ambiente no arquivo .bashrc, toda vez que o bash iniciar ele vai ter essas variáveis. E agora podemos instalar alguns programas gráficos que eles vão aparecer do lado de fora, no Windows mesmo. Vamos instalar o gvim que eu gosto, o terminal Tilix que é o mesmo que eu instalei no tutorial de Ubuntu e o pacote dbus-x11 que programas gnome precisam.

Agora, precisamos instalar o tal cliente X, que como eu disse antes tem opções gratuitas mas eu pessoalmente gosto do X410 que eu já tinha comprado então vamos instalar pela Windows Store. Quando terminar e ele carregar, não esquecer de habilitar pra poder receber conexões da rede pública, se for a primeira vez o Windows Firewall vai apitar pra liberar acesso, libere e pronto. De volta ao bash, como só editamos o script bashrc precisamos ou deslogar e logar de novo ou só recarregar o bashrc com o comando source. Agora podemos abrir o gvim e olha só, abriu fora da máquina virtual, como um programa normal de Windows. Mesma coisa o Tilix e nele podemos configurar a aparência, escolhemos o tema Material … melhor o Monokai Dark … pronto, e agora podemos configurar o tamanho das fontes.

Mas pra mudar o tema geral, das bordas e tudo mais não vamos poder usar o gnome-tweaks. Como não estamos carregando todos os serviços que o gnome precisa pra rodar, se gastar algum tempo em tentativa e erro uma hora o gnome tweaks deve funcionar, mas por agora podemos simplesmente editar manualmente o arquivo de configurações em .config, gtk-3 settings.ini. Dentro dele habilitamos o modo dark, depois declaramos o nome do tema que é o Flat Remix GTK Blue Darker e finalmente o nome do pacote de ícones que é o Flat Remix Blue Dark.

Agora precisamos de fato baixar esses temas e ícones e aqui é a mesma coisa que já fizemos no episódio de Ubuntu, vou seguir exatamente o mesmo tutorial de antes. E como é a mesma coisa, vamos acelerar isso aqui.

Pronto, agora podemos chamar o Tilix de novo boom, tá bem mais bonito não acham? O Gvim também ficou um pouco melhor mas ele ainda está mais cru porque não carreguei nenhum dotfiles que configura ele como o Yadr que mostrei no episódio de Ubuntu.

Até agora eu estava usando o programa Command que vem com todo Windows. A Microsoft veio modernizando o antigo Command pra aceitar comandos de VT100, ANSI e tudo mais e com isso ele hoje é capaz de renderizar corretamente, vocês podem ver que as cores ANSI funcionam perfeitamente. Ele ainda tem cheiro de coisa velha mas comparado a como era até as primeiras edições do Windows 10, foi uma evolução considerável. Mas terminais mais maduros como o Tilix que acabamos de instalar ou mesmo o terminal padrão de um MacOS ainda são melhores.

Em vez de mexer demais no código velho do antigo Command, eles resolveram codificar um novo e com isso existe o programa simplesmente chamado de Terminal que você pode instalar via o Windows Store. Ele ainda está em desenvolvimento e por isso se chama Preview, mas mesmo no estágio atual ele já é muito melhor que o Command, inclusive ele é acelerado via DirectX então coisas como scrolls longos renderiza muito rápido. Vamos abrir e veja como ele é mais moderno com suporte a tabs. A configuração ainda é toda em texto, o que eu não acho ruim. Por exemplo, podemos mudar o tamanho das fontes de todos os perfis. Vale a pena brincar nesse arquivo um pouco pra customizar como você quiser.

Você pode usar esse novo Terminal pro WSL 2 até pra rodar o antigo CMD se precisar de scripts de batch antigos ou também rodar Powershell em outra aba. Ele é totalmente configurável e customizável, suporta abas e pode ser uma boa opção. Eu particularmente prefiro carregar o próprio Tilix e usar um terminal de verdade que suporta split de tela. Tem gente que prefere usar o TMux pra splits mas pro meu workflow um simples split do próprio terminal é suficiente. Se quiser ver o Tmux em funcionamento, veja meu vídeo de Ubuntu onde eu mostro como instalar e usar.

Mas só instalar o Ubuntu é muito fácil. Agora é uma boa hora de mostrar algo diferente. Hoje a Microsoft suporta o Ubuntu, o Fedora e o OpenSuse além de uma distro paga feita especialmente pro WSL 2 que é Pengwin, que se não me engano é uma derivação de Debian. Mas eles tem receitas no GitHub de como você pode empacotar qualquer outra distro de Linux. Desde o WSL 1 havia uma versão de Arch que você podia instalar e eu testei e vi que eles deram suporte pro WSL 2 também, então porque não testar? Mas, aviso que essa demo é meio experimental, ele não tem suporte da Microsoft e muitas coisas não funcionam perfeitamente ainda. Se você for experiente em Linux talvez consiga achar os workarounds, mas se for iniciante, por agora é melhor ficar no Ubuntu mesmo.

Vamos no Google, pesquise por ArchWSL e o primeiro link deve ser a página de GitHub do projeto. Lá tem todas as instruções que você precisa. Baixe o zip, descompacte em algum lugar como no seu diretório de usuário do Windows. Dentro vai ter um executável de Arch, dê duplo clique ou abra o novo Terminal, navegue até o diretório, digite arch e execute. Esse instalador faz menos coisas que o de Ubuntu, o que é esperado pra filosofia Arch de fazer você configurar tudo. Ele vai te logar como root direto. A primeira coisa a fazer é colocar uma senha pro root. Agora vamos criar um novo usuário não-root, configurar a senha, e abrir o visudo pra dar permissão de sudoer pra esse novo usuário. Dessa forma você vai poder logar como esse usuário em vez de root e ter acesso a sudo. … Feito isso podemos sair da sessão do Arch de volta ao Command do Windows e configurar o arch pra carregar com esse novo usuário.

Feito isso precisamos inicializar o pacman que é o gerenciador de pacotes, isso só precisa ser feito uma vez. Uma vez inicializado, mesma coisa de antes, eu gosto de atualizar os pacotes todos e fazemos isso com o comando pacman traço Syu. No Ubuntu começamos instalando pacotes como o build-essential pra ter o toolchain de desenvolvimento. Com o pacman instalamos pacotes como o base, base-devel e vamos aproveitar pra instalar também o gvim, tilix e git.

Seguindo os mesmos passos, vamos exportar as mesmas variáveis de ambiente pra rotear o X…. Pronto, podemos carregar o Tilix. Porém aqui já vemos um problema dessa versão. Programas gnome precisam do dbus pra comunicação interprocessos, incluindo gerenciar configurações. Mas veja as mensagens de aviso dizendo que não encontra o dbus-launch. Um workaround pra isso é chamar o dbus-launch manualmente. De novo, eu não parei pra investigar a fundo quais dependências estão faltando ou serviços que não estão carregando. Se você souber como corrigir isso, não deixe de mandar nos comentários abaixo.

Aliás, uma coisa que notei é que tanto o Ubuntu como esse Arch de WSL não bootam com o systemd, ou qualquer outro init system então nenhum serviço carrega e comandos como systemctl pra iniciar ou parar serviços não vão funcionar. Você precisa carregar serviços manualmente se precisar.

De qualquer forma, só pra mostrar como as coisas são muito parecidas, vou acelerar de novo pela parte que eu baixo os temas Flat Remix, editar o arquivo settings.ini manualmente e … pronto, tilix carregado com o novo tema, igualzinho antes.

E com isso temos um Arch instalado, você pode seguir qualquer tutorial normal de como configurar seu Arch como ambiente de desenvolvimento ou seguir as mesmas instruções que eu mostrei pro Ubuntu.

Agora, vamos voltar pro Ubuntu. Pra demonstrar que tudo funciona, vamos seguir meu tutorial e instalar o bom e velho ASDF como já mostrei antes. …

Não esqueça de colocar os scripts de asdf no bashrc ou equivalente do seu shell pra iniciar sempre que se logar. …

Feito isso podemos instalar o plugin de ruby ….

Agora podemos listar as versões de Ruby disponíveis …

Vamos escolher a mais recente, a 2.6.4 e instalar ….

Oops faltou instalar mais dependências de desenvolvimento do sistema, então vamos começar pelo pacote build-essential.

Ainda temos mais dependências, então vamos instalar … Oops ele não acha o libgdbm3 então vamos tentar libgdbm Oops ele não acha libgdbm também, ok deixa ele pra lá, não vamos precisar disso agora mesmo

Pronto, pacotes instalador, então vamos instalar o Ruby 2.6.4. Demora um pouco mas uma hora ele termina.

Não esquecer de configurar essa versão pra ser a padrão do sistema Ruby instalado, vamos instalar a gem do Bundler

E finalmente, vamos instalar as gems do ruby on rails

Como o Rails hoje usa muito javascript também, inclusive ele integra com o yarn e com webpack, melhor instalar o plugin de nodejs do asdf. …

Pronto, mesma coisa, vamos listar as versões disponíveis e ... escolher a mais recente, a 12.9.1

Oops faltou configurar as chaves gpg dos repositórios, então vamos na página de github do plugin

Basta copiar esta linha e executar pra instalar as chaves Pronto, chaves configuradas, agora podemos tentar instalar a última versão de novo

Quando terminar, não esquecer de configurar essa versão como a global do sistema Esse passo nem precisa na real, mas eu tenho costume forçar a atualização do npm pra garantir que estou com a versão mais nova.

Vamos instalar o yarn também

Agora, vamos brincar um pouco e criar um novo projeto Rails do zero com seu gerador.

Oops falhou porque faltou instalar o sqlite3. Isso é opcional mas como eu não indiquei nada no gerador ele por padrão espera ter o sqlite3 então vamos instalar.

Como parou o gerador no meio, então vamos entrar no diretório do projeto e rodar manualmente o comando bundle install pra terminar de instalar as dependências.

Agora vamos carregar o rails server pra testar a página de Bem Vindo e oops, como o gerador tinha parado aquela hora ele não inicializou o webpack, então vamos fazer isso manualmente com a task webpacker dois pontos install.

Pronto, agora sim vamos carregar o rails server.

E com meu navegador Opera de Windows, podemos carregar o localhost porta 3000 e veja que funcionou! O WSL mapeou corretamente a porta 3000 de dentro da máquina virtual pra estar acessível do lado de fora no Windows.

Como última parte desse demo, vamos continuar o projeto gerando um controller de rails pra testar mais um pouco. Mas e agora, como vamos editar esses novos arquivos? A partir daqui eu abriria meu gVim devidamente configurado mas eu sei que muita gente migrou pra Visual Studio Code hoje, que eu também concordo que é um editor de textos e uma IDE excepcional. Altamente recomendado então vamos na página dele e baixar o instalador. E sim, vamos baixar o instalador de Windows e instalar do lado de fora no Windows.

Mas e agora? Como o VS Code de Windows vai editar os arquivos do meu projeto que está dentro do WSL? Aqui começa uma das vantagens do WSL ser desenvolvido pela Microsoft, o VS Code automaticamente detecta a existência do WSL, detecta que o Ubuntu é a distro padrão, instala os plugins adequados e agora podemos abrir uma nova janela do VS Code que vai estar conectado no WSL, olha a barra de status, no canto inferior esquerdo, e veja que ele está abrindo o WSL.

Com isso quando mandamos abrir uma pasta ele vai explorar o filesystem de dentro do WSL automaticamente apontando a partir do diretório do meu usuário e de lá podemos abrir o projeto Rails que acabamos de criar.

Então vamos procurar nosso arquivo de index html e editar alguma coisa.

Vamos também editar o arquivo de rotas pra colocar esse controller como raiz.

Agora vamos reiniciar o servidor e recarregar a página no navegador e boom, olha só, funciona de boas. Apesar do WSL 2 ainda não ser tão completo quanto rodar uma máquina virtual de verdade, ele é leve e simples o suficiente pra ser tipo um runtime que roda em background, você pode usar o Terminal no Windows e o VS Code no Windows mesmo e desenvolver quase como se estivesse num Linux de verdade. Pra muitos casos de uso isso pode ser exatamente o que as pessoas precisam.

A última questão que todo mundo tem é, mas e o Docker? Funciona? Vamos no google de novo procurar um tutorial de docker pra ubuntu, os da digital ocean costumam ser bons então vamos nele.

Vamos seguir passo a passo como ele manda, primeiro instalar a chave gpg dos repositórios oficiais do docker.

Adicionar os repositórios como fontes pro apt

Rodar o apt update pra atualizar

Garantir que vamos instalar dos repositórios do docker

Finalmente instalar o docker propriamente dito

E iniciar o serviço com o systemctl e oops

Como eu disse antes, o WSL não boota com o systemd então não existe suporte ao init system e o comando systemctl não vai funcionar.

Mas, podemos executar os daemons do Docker manualmente, e pronto, é meio chato ter que fazer isso toda vez que o WSL reiniciar, mas é melhor que nada.

Continuando o tutorial, vamos adicionar o grupo docker ao nosso usuário. Agora a gente ou desloga fechando o terminal ou só fazemos su pra abrir uma nova sessão direto.

Pronto, checando se tá tudo ok e agora podemos rodar o famoso hello world e ver que tudo funciona.

E pra garantir vamos um passo além e instalar o Postgres no Docker. E mesma coisa, primeiro vamos no Google procurar um tutorial qualquer.

Primeiro, puxamos a imagem do docker

Agora criamos diretórios pra guardar os bancos de dados

Finalmente podemos subir o Postgres dentro do Docker com esse comandão aqui, que vai configurar coisas como o mapeamento de portas.

Pra demonstrar que é o docker nativo de verdade rodando, se rodarmos o comando ps aux podemos ver todos os processos rodando dentro da máquina virtual e temos o postgres aparecendo como deveria. Se você assistiu meus episódios sobre virtualização e containers já entendeu que Docker não é virtualização. Na prática tudo que você roda via Docker roda normalmente como um processo no seu sistema, mas o Docker usa recursos da Kernel do Linux como cgroups pra fazer o sistema operacional mentir pro processo, fazendo esse processo acreditar que está rodando sozinho na máquina.

Como eu já tinha mostrado no episódio de Ubuntu, podemos navegar pro diretório /proc no subdiretório que é o pid do processo e oops, pid errado, vamos listar de novo e pegar o pid certo e navegar de novo

Pronto, agora podemos listar o conteúdo do meta arquivo cgroups e ver como ele está sendo mascarado pelo docker.

Pra todos os efeitos e propósitos, o processo do postgres acredita que está rodando sozinho na máquina, e pra gente do lado de “fora” é só mais um processo rodando como se tivesse sido instalado fora do Docker. Por isso containers de Docker são ordens de grandeza mais leves e performáticos do que uma máquina virtual, porque na prática não existe um hypervisor envolvido, é só uma casquinha muito leve do próprio kernel. O kernel precisa ter esse suporte e por isso não funcionava no WSL 1 porque é difícil recriar isso só com um wrapper em cima da kernel do NT e eles nem chegaram a tentar eu acho. Então o Docker de Windows, pra rodar containers de Linux precisava de fato executar um Linux no Hyper-V pra criar os containers dentro. Aqui é a mesma coisa já que o WSL 2 é uma máquina virtual rodando uma kernel de Linux de verdade, entenderam?

Agora podemos conectar nesse postgres dentro do container através da porta que o Docker mapeou pra fora. Vamos seguir o tutorial e oops ainda não temos o cliente de postgres instalado então vamos seguir o que ele manda instalar.

Oops, ainda falta mais um pacote, vamos seguir a instrução e instalar e agora sim, cliente instalado, vamos rodar o comando de novo.

Pronto! Estamos conectados no postgres, rodando dentro do container do Docker.

Podemos rodar docker ps pra ver quais containers estão sendo executados e podemos pegar o pid do container e com ele rodar comandos como o docker stop pra parar o container e assim derrubar esse postgres.

Eu até acho que nesse ambiente de WSL 2, onde ele não sobe o systemd no boot e por isso não sobe daemons automaticamente

E é isso, o WSL 2 é basicamente um Hyper-V customizado pra rodar um kernel de Linux modificado pela Microsoft. E como a licença GPL obriga qualquer empresa a abrir o código fonte se for modificado, a Microsoft mantém o fork com as modificações no GitHub, e eu vou deixar o link nas descrições abaixo também. Eles vão sempre manter a versão estável mais nova e não devem ficar dando suporte pra kernels antigas, então você sempre vai ter a opção de rodar as distros mais novas. Se precisar rodar Linux velhos, você mesmo vai ter que instalar e configurar tudo manualmente numa máquina virtual Hyper-V do jeito antigo. O WSL 2 é um facilitador pro Hyper-V com um Linux melhor integrado.

Na prática, o WSL 1 era algo parecido com o Wine só que ao inverso. O WSL 2 é mais parecido em conceito com o coLinux, mas na prática é um Linux rodando dentro de uma máquina virtual. Então se você já usava Linux em Virtualbox ou VMWare na prática não tem grandes vantagens, especialmente porque o WSL não expõe tantas opções de configuração como um VMware. Por outro lado a grande vantagem é o suporte oficial da Microsoft, ou seja, pela primeira vez é uma opção padrão. Veja a integração do Visual Studio Code com o WSL por exemplo. Uma vez que o WSL é um componente oficial no Windows, outras soluções podem começar a se integrar e criar um ecossistema

Eu gostava bastante do conceito do WSL 1, que seria o único caminho pra ter uma camada Linux integrada de verdade na fundação do Windows, o mais próximo que já se chegou do que se tem um macOS sem precisar reescrever todo o sistema operacional. Mas, eu entendo que era bem pouco prático dar suporte. Então o WSL 2 é um bom meio do caminho. Muita gente precisa estar no Windows seja pra usar programas corporativos que só existem em Windows, ou rodar pacotes como os da Adobe que não existem pra Linux ou mesmo fazer desenvolvimento em .NET pra Windows. Agora existe uma opção oficial pra rodar ferramental Linux lado a lado, com uma boa integração. Ele é mais limitado, ainda existem alguns bugs, mas com o tempo isso deve melhorar.

Na prática, se você já usa Virtualbox ou Vmware pra desenvolver em ambiente Linux, não existe de fato muita vantagem em usar o WSL 2. Mas se você quer integrar desenvolvimento de projetos em Node ou Python dentro do Linux junto com outros projetos no Windows usando o mesmo Visual Studio Code, por exemplo, é uma ótima opção. Aliás, apesar de existir um certo suporte nativo a rodar coisas como Node, PHP e Python no próprio Windows, eu altamente não recomendo, o certo é rodar dentro do Linux e ter acesso a tudo que esses ecossistemas oferecem. Toda versão de uma ferramenta Linux no Windows tem limitações, e rodando no WSL 2 ou numa máquina virtual elimina essas restrições. Evite ao máximo rodar ferramentas de Linux compiladas pra Windows, você sempre vai estar usando menos do que as ferramentas oferecem. Nesses casos o WSL 2 interessante pra desmotivar a existência dessas alternativas e simplesmente rodar o original num Linux de verdade, agora suportado pela própria Microsoft.

O único cenário onde eu ainda recomendaria Linux instalado nativo é se você está brincando de machine learning com ferramentas que precisam de acesso direto à GPU. Isso nem o WSL 2 e nem muitas máquinas virtuais vai conseguir te dar. Você precisa de acesso direto ao hardware. O WSL 2 hoje não tem como expor a GPU pra dentro da máquina virtual. Esse é um dos casos que é melhor uma configuração de dual boot se você também precisa de Windows, e se manter num Linux instalado nativamente na sua máquina.

No caso de desenvolvimento Web normal, onde você quer ter um postgres, redis, nginx, nodejs, rails, elixir, o WSL 2 é uma opção excelente e com a integração do Visual Studio, eu acho que se torna um pacote bastante atraente. Eu estou usando os dois, numa máquina tenho um WSL 2 e em outra tenho o VMWare. Como meu dia a dia não é mais ocupado por programação na maior parte do tempo, então pra mim o impacto é muito menor. Se você já testou essas opções, compartilhe sua experiência com o pessoal nos comentários abaixo. Se curtiu o video deixe um joinha, assine o canal e não deixe de clicar no sininho pra não perder os próximos episódios. Por hoje é só, a gente se vê, até mais!


[Akitando] #61 - Meus Primeiros 5 Anos | 1990-1995

DESCRIPTION

Hoje quero voltar a contar algumas histórias. Se vocês assistiram os vídeos do começo do canal já devem ter visto alguns trechos de como eu era no começo de carreira.

Mas desta vez quero ir bem pra trás, até o momento zero! Muita gente tem essa dúvida: como começar? Como devo escolher o que aprender? Como eu sei que não estou perdendo tempo? E pra ajudar a responder essas perguntas vou dar como perspectiva como foi pra mim mesmo, dos 13 aos 18 anos, até o 1o ano de faculdade.

Esse período compreende os anos de 1990 a 1995. E eu acho que você pode se surpreender com a minha resposta a essas perguntas.

Vídeos complementares:

  • Procure o que você Ama ... só que não! (https://www.youtube.com/watch?v=A2-yU3YjB1U)
  • Dúvida! Devo fazer faculdade? (https://www.youtube.com/watch?v=XWVcF7BoCSc)
  • O Diário de Henry Jones (https://www.youtube.com/watch?v=ii5Q2fCl8C0)
  • Devo fazer faculdade? (https://www.youtube.com/watch?v=iRjEa7N8wEo)
  • Sua Linguagem é Especial? - Parte 1 (https://www.youtube.com/watch?v=NwAvovzHRDU)
  • Sua Linguagem é Especial? - Parte 2 (https://www.youtube.com/watch?v=O4CWVQLbi48)
  • O que eu devo estudar? (https://www.youtube.com/watch?v=Ll1uAZ-WRa0)
  • Minha Máquina do Tempo (https://www.youtube.com/watch?v=0KNGiOp7eoE)
  • A Dimensão do Tempo para Iniciantes em Programação (https://www.youtube.com/watch?v=Qb5b8ZE9tIY)

Links:

  • Globo Reporter 1991 - A Febre dos Videogames (https://www.youtube.com/watch?v=8GirQPUdlTM)
  • Encoded, Decoded How Usenet—a protocol intended for conversations—was forever changed once the public figured out you could transfer binary files through it. (https://tedium.co/2017/10/03/usenet-binaries-history/)

SCRIPT

Olá pessoal, Fabio Akita

Muita gente se pergunta como aprender as coisas. Como aprender Linux? Devo escolher Java primeiro ou Python primeiro? Melhor esquecer Web e ficar no Mobile? O que devo aprender primeiro? Eu sempre venho respondendo essas perguntas no canal de diversas formas, mas cada caso é um caso.

Muita gente olha pra mim, hoje, com quase 30 anos na área e por alguma razão tem dificuldade de imaginar como chegar no mesmo nível. Pra quem esta chegando agora no canal eu fui programador minha vida inteira na última década só que passei a fazer outras coisas como palestras, geração de conteúdo e abri minha própria empresa.

Na realidade, a maioria das pessoas superestimam o que eu realmente sei e aí parece ainda mais longe. Mas todo mundo também esquece que qualquer um com X décadas de carreira teve seu dia 1 igualzinho todo mundo, sem saber nada de nada, zero conhecimento, zero experiência, e zero noção de pra onde ir. Isso é o normal, ninguém nasce sabendo o que vai fazer pro resto da vida.

Eu já vim contando algumas histórias, como no Diário de Henry Jones, na Dimensão do Tempo, Sua Linguagem é Especial, e neste episódio quero contar alguns trechos de história dos meus primeiros cinco anos, que encaixa entre esses outros episódios. Vocês vão ver que no geral as histórias não são nada especiais, nenhum episódio sobrehumano tipo entrar no MIT com 15 anos ou algo assim. Esses casos existem mas não sou eu.

Essa história é para os iniciantes ansiosos. Muitos que estão assistindo o canal, ou são jovens que estão pensando em entrar na faculdade ou estão nos primeiros anos, ou então pessoas mais experientes nos seus 30 ou 40 querendo pular pra área. Pra vocês que querem saber como muitos de nós que já passamos pela área começamos. O objetivo não é dar uma receita pra ser copiada, só dar um contexto e perspectiva. Eu realmente acho que não é tão importante ficar encanado demais nos primeiros anos. Iniciantes tem muito pouca informação pra ficar tentando escrever em pedra "é isso que eu vou fazer pelos próximos 5 anos". Permita-se explicar.

Meus primeiros anos tendo exposição à computação foi do meio pro fim dos anos 80. Eu não tinha computador na época, eu tinha primos e amigos da escola que tinham e eu podia mexer quando ia na casa deles. Na minha cabeça um computador era uma coisa cara e inacessível pra mim, então eu só sonhava com o dia que eu teria meu próprio computador. Pensa que crianças como eu naquela época liam gibi da Turma da Mônica ensinando o que era o Dragão da Hiper Inflação e o Leão dos Impostos destruindo tudo. A gente cresceu entendendo que era normal nosso dinheiro não só não valer muita coisa. Felizmente minha família, que veio do interior tentar a vida na cidade grande, não era pobre, mas eu tinha noção que tinha coisas que eu não devia esperar ter.

Em 88 eu tinha uns 11 anos de idade, meu primeiro contato com alguma coisa que lembra código era no MSX hotbit do meu primo onde a gente minimamente precisava digitar um comando como LOAD pra carregar os games das fitas cassete. Foi assim que eu comecei a ter noção da idéia de que a gente dava comandos pra máquina executar. Eu via os códigos em revistas como a Micro Sistemas mas eu não tinha computador pra digitar. Eu via as propagandas de computadores da época como o MSX, os clones de Macintosh como o Unitron, os CP-400 ou CP-500 da Prológica, eram meus sonhos de consumo. Eu não tive nenhum deles, só pude mexer no dos outros de vez em quando.

Meu primeiro contato com programação de verdade foi em 1990, eu tinha 13 anos. Se eu não me engano alguém de telemarketing ligou em casa oferecendo desconto num curso de computação, e meus pais resolveram do nada me colocar nesse curso. Fomos eu e meu pai na verdade, aprender MBasic e Wordstar na Data Center, que já nem existe mais. Eu ouço as pessoas atrelando o aprendizado com algum objetivo fixo. Eu não entendo isso. Meu único desejo naquela época era programar qualquer coisa num computador, só isso, o único desejo de dar um comando e o computador me responder. Não tinha nenhum objetivo concreto, eu não estava pensando em aprender a programar pra ter uma carreira, ou porque eu queria abrir um negócio, nada disso. Era 1990, pensa que foi o ano que saiu Esqueceram de Mim e De Volta pro Futuro 3 nos cinemas, eu tinha 13 anos e zero noção do mundo.

Não lembro se foi nesse mesmo ano ou no ano seguinte, mas provavelmente entre 1990 e 1991, acho que meus pais se convenceram que eu realmente gostava de programar já que eu não desisti do curso, e modéstia a parte, eu entendia o Basic bem mais rápido que meu pai na época. Eu tinha um tio que era analista de sistemas, se não me engano ele fazia sisteminha pra uma loja de shopping e outros projetinhos pra outras pequenas empresas. Ainda estávamos na reserva de mercado, pra conseguir um bom computador, só indo no Paraguai, e esse tio conhecia alguém que trazia. Que nem hoje em dia que todo mundo conhece alguém que trás hardware de Miami. A gente não tinha Mercado Livre ou Gearbest, o mais próximo disso era a página de classificados nos jornais.

Enfim, meu pai resolveu investir num PC XT, com poderosos 1 MB de RAM, muito acima dos normais 640 kilobytes, um drive de floppy disk de 5 1/4" e outro mais moderno de 3 1/2" que suportava disquetes dupla face!!E poderosos 10MB de HD, que sei lá porque a gente chamava de Winchester naquela época. E junto um monitor CGA de 4 tons de fósforo verde e uma impressora matricial Epson FX 1050. O computador vinha com MS-DOS 3 e eu tinha GW-Basic e meu tio deixou instalado dBase III. Isso deve ter sido bem caro, hoje eu estimo que deve ter custado quase 2000 dólares em dinheiro dos anos 80! Eu tinha noção que era bem caro. E era basicamente isso que eu tinha. E era tudo que eu precisava, a partir daí eu me virei.

Pra ter uma noção de como eu tava feliz eu queria fazer meu próximo trabalho da escola no computador e imprimir na novíssima impressora, claro! Mas eu sequer tinha um editor de textos, e não tinha internet pra baixar. Como que eu fiz? Bom, a única coisa que eu sabia era Basic. Pois bem, abri o interpretador e fui digitando linha a linha do trabalho usando o comando PRINT. Detalhe, que eu não sabia e nem lembro se dava, pra navegar de volta pras linhas de cima. Uma vez que digitou era difícil de corrigir depois. Então da primeira vez eu tive meio que digitar do começo ao fim sem errar, felizmente era um texto curto. E foi assim que imprimi meu primeiro trabalho!

Em pouco tempo eu comecei a juntar mais programas. O DOS 5 eu acho que foi onde apareceu o editor Edit e o Quick Basic. Eu consegui uma cópia do Wordstar, do Lotus 1-2-3. A primeira apostila de dBase III eu acho que peguei emprestado com um primo mais velho que tava estudando. E o que eu fiz na época? Lembram que no episódio de Henry Jones eu contei que antes de ter um computador um hobby que eu tinha era copiar textos da enciclopédia na máquina de escrever? Então, eu copiei digitando, página a página dessa apostila no Wordstar pra imprimir minha própria versão. Detalhe que eu usei pouco porque depois de copiar todas as sei lá, 100 páginas da apostila, eu acabei aprendendo o dBase III.

Os anos meio que não são exatos mas acho que foi alguns meses depois, talvez final de 91? Meu tio perguntou se eu aprendi o tal dBase e me convidou pra ajudar ele nas férias da escola. E foi assim que consegui meu primeiro estágio. Ele passava os dias na tal loja, visitava clientes, e chegava no fim do dia com especificações e explicava o que ele queria, dai eu passava o dia seguinte na casa dele programando. Ele tinha um computador muito melhor que o meu, acho que já era um 386 com monitor VGA e eu obviamente já tava muito feliz só de poder usar um computador mais potente, e colorido! E assim eu comecei a migrar do dBase pro Clipper Summer 87 que tinham linguagem compatível, e logo na sequência pro Clipper 5. Fiz programas de controle de estoque, programa pra gerenciar um escritório de advocacia, programa que gerenciava não lembro o que de avião particular, e coisas assim.

Sendo bem honesto eu não tenho idéia de quanto do meu código era realmente aproveitável, mas eu seguia o exemplo dos outros programas do meu tio e ouvia os feedbacks dele no dia seguinte. Lembrando que eu programava do computador dele direto porque era mais eficiente, não tinha internet pra eu poder trabalhar remoto, não tinha Git, não tinha nada disso ainda. Controle de versão era basicamente um conceito desconhecido. Backup era em disquete. Assim foi uns dois meses das minhas férias de verão quando eu tinha uns 14 anos.

Clipper pra mim era o que provavelmente Javascript, ou Java, ou Visual Basic é pra muitos de vocês: a minha primeira linguagem favorita. Eu achava que dava pra fazer tudo em Clipper. Meu editor favorito na época era o Norton Editor, meu tio me apresentou pro Sidekick que era um programa residente em memória no DOS que tinha um help pros comandos do Clipper, utilitários como calculadora. Era tudo que eu precisava no meu ambiente de desenvolvimento. A partir desse ponto meio que já tava decidido que eu provavelmente ia escolher computação como faculdade. Outro contexto: fazer faculdade nunca foi uma opção, eu sempre encarei como o próximo passo natural depois do colégio, então eu nunca parei pra pensar em não fazer. De qualquer forma, depois desse estágio com meu tio, eu peguei mais alguns projetinhos por conta. Lembro de ter feito um programa de controle de estoque pra uma loja de bebidas do bairro por exemplo, e vocês não imaginam como eu fiquei contente quando consegui fazer um joguinho e meu próprio editor de textos simples, tudo em Clipper.

Pra ser mais honesto, ainda havia uma outra direção que eu ficava flertando. Desde criança tinha outra coisa que eu gostava de fazer e era desenhar. Olhando em retrospecto, claro que eu não era tão bom quanto eu achava que eu era. Mas vamos lá, eu era um pré-adolescente. A gente nunca tem noção de quanto é bom ou ruim em alguma coisa. Mas de qualquer forma eu gostava de desenhar. Eu tinha poucos amigos na época, eu nunca joguei bola na rua ou coisas assim, eu passei minha infância basicamente dentro de casa. Lá pelos 14 anos um menino da rua que estudava na mesma escola que eu acabou fazendo amizade comigo e foi interessante porque ele era o oposto de mim: eu sempre fui o japonês CDF introspecto e quieto e ele era o extrovertido. Vocês tem que entender que famílias japonesas eram bem mais rígidas, particularmente nos anos 80. De qualquer forma, nessa época a gente tava aprendendo computador ao mesmo tempo e ele sendo mais atirado conseguiu um bico pra gente na Fenasoft. Antigamente existiam 2 grandes feiras de tecnologia, a Fenasoft e a Comdex, coisa tamanho da Feira do Automóvel de hoje, que acontecia no Centro de Convenções do Anhembi.

Esse meu amigo era um ano mais novo que eu mas o filho da mãe era ligeiro, e inteligente. Ele puxou conversa num quiosque da Brasoftgames no shopping e eles ficaram impressionados e convidaram ele pra trabalhar na Fenasoft apresentando jogos. Porém teve um problema, a idade limite pra trabalhar era 15 anos e ele sendo um ano mais novo acabou não podendo ir e ele me mandou no lugar dele. Enfim, como eu disse o evento era gigantesco, ocupava o pavilhão do Anhembi inteiro, então imagina o tamanho. A estande da Brasoftgames era um dos maiores, com um super telão com palco e vários quiosques menores. Acho que o ano era 1992 e as novidades eram os novos kits multimidia que adicionavam CD-ROM, com super velocidades de 2x e 4x eu acho, e placas de som, os Adlib e Soundblaster. E jogos como Star Wars Rebel Assault, X-Wing e Tie Fighter.

Eu contei tudo isso pra chegar no seguinte episódio. E aqui eu devo dizer que não lembro de todos os detalhes mas se não me falha a memória aconteceu a seguinte situação. Imagina que a gente ficava nesses quiosquinhos espalhados pelo estande gigante, com um microfoninho que saia nas caixas de som daquela época, apresentando os jogos, e juntava um pessoal ao redor pra ver. De vez em quando nosso chefe subia no palco do telão e falava algumas merdas e fazia piadas sem graça. E aqui vem a parte que eu não lembro se foi assim mesmo que aconteceu mas eu lembro que eu tava ficando irritado com as piadas sem graça e as informações erradas que ele falava. E meio sem perceber, quando isso acontecia, meu quiosque que ficava perto do palco era interrompido porque o som do palco era mais alto. O que eu fiz? Eu comecei a gritar mais alto no meu quiosque. E eu acho que tava funcionando, o povo ficava olhando minha apresentação. Daí o chefe do meu chefe apareceu no meu quiosque uma hora.

Eu não entendi nada, mas acho que ele disse alguma coisa tipo "você, pra lá" e me mandou pro palco. De novo, vocês lembram que eu falei que eu sempre fui o japones CDF introvertido que nunca fala. Ênfase no "nunca". Eu aquele tipo que na escola fazia trabalho em grupo sozinho sem ajuda dos outros só pra não precisar falar na frente de classe, e deixava alguém só falar no meu lugar. Já era um esforço sobrehumano eu estar naquele quiosquinho me acostumando a apresentar pra meia dúzia de pessoas aleatórias. Eu não sabia falar em público, nunca foi natural, especialmente aos 15 anos. E de repente lá estava eu, num palco, numa feira, na frente de um telão e com uma multidão olhando pra mim, esperando eu apresentar.

Esse foi um daqueles momentos "ou vai ou racha". A imagem que me veio à cabeça foi que quando eu tinha uns 10 anos ou menos, eu tive que fazer uma pequena apresentação em público, num eventinho da comunidade japonesa do bairro, eu gaguejei, muito, eu desesperei e eu fugi do palco. Foi um trauma e agora, 5 anos depois, eu tive quase certeza que ia acontecer a mesma coisa. E pra minha surpresa eu tomei uma decisão consciente de não fugir. Essa foi a primeira vez que eu consegui falar em público direito e foi a primeira vez que eu me dei conta que talvez fosse possível eu falar em público. Muita gente me conhece hoje como palestrante, vocês não tem idéia de como foi difícil eu aprender isso. Eu nem sei dizer se a apresentação foi boa ou não, mas como não me expulsaram do palco, vou acreditar que foi bom o suficiente.

Porém, meu chefe, o alemão que me irritava quando subia no palco, acho que não gostou do chefe dele ter me colocado no palco porque no ano seguinte ele me barrou de trabalhar de novo. Mas aí aquele meu amigo conseguiu um bico no concorrente no mesmo evento e fomos pra Magnasoft. Com 16 anos eu já tava um pouco mais confiante e acho que foi nesse evento que outro episódio interessante aconteceu. Um dos programas que eu tinha que apresentar não era grandes coisas, era algum tipo de pacote educativo que tinha um programa nível de Paintbrush do Windows, pra desenhar. E eu disse que eu gostava de desenhar. Um dos jogos que eu gostava naquela época pela arte era o Tazmania e eu fiquei lá desenhando. Pensa que desenhar com um mouse de bolinha era muito ruim, mas eu já estava acostumado. E por acaso apareceu um jornalista que gostou daquilo e me convidou pra aparecer em algum jornal local da época. Como eu disse antes, eu não tinha muita idéia se o que eu desenhava era considerado bom ou não, mas isso foi um empurrãozinho.

Eu sempre fui fã de quadrinhos, games, mangás, animes, tokusatsu, era o que eu desenhava. Das revistas de videogames, como a Ação Games e Videogame eu era fã de alguns gamers da época e por acaso conheci um deles nessa Fenasoft, o Toni Ricardo Cavalheiro. Se vocês tem perto da minha idade talvez se lembrem desse Globo Reporter de 1991 ou 1992.

E ele aparecia lá também. Por causa desses eventos nessa época eu finalmente fiquei mais comunicativo e fiz amizade com ele. Cheguei a ir na casa dele com disquetes copiar jogos, e foi como tive acesso a coisas como Doom, The Lost Vikings, Lemmings e mais. E ele me apresentou pra revista Videogame, onde ele trabalhava fazendo reviews dos jogos. Naquela época, pra conseguir programas e jogos você tinha que pegar sua caixa de sapatos com disquetes, pegar um ônibus e ir na casa de alguém que tinha. Vocês não tem idéia de como a vida de vocês é fácil com internet hoje.

Eu pensei em fazer ilustrações pra revista já que apesar de gostar de games eu nunca fui nível de gamer profissional. Levei meu portfolio pra editora, e eu não sei se o editor não curtiu meus desenhos ou se foi sincero, mas ele começou perguntando se eu sabia fazer ilustração em computador, e claro, eu não sabia. Esses desenhos que estão aparecendo aqui do lado são meus lápis de 91 a 92 se não me engano. E ele disse "pois é, hoje em dia as ilustrações são todas digitais e vetoriais, precisa saber usar um CorelDraw". Naquele mesmo dia eu fui pra uma livraria e comprei um livro de CorelDraw 3.

Passei o próximo mês aprendendo a ilustrar com CorelDraw. Em 1993 eu tinha ganhado meu segundo computador, um 386 de poderosos 66Mhz e acho que já tinha 2 ou 4 MB de RAM com uns 30 MB de HD. E agora eu tinha um monitor VGA. Coloque em perspectiva, era um monitor de 640 x 480 de resolução. O que chamamos de HD hoje em dia é 1920 por 1080, era 4 vezes menos densidade de pixels e tinha só 256 cores. Ah sim, e o mouse mais barato que você acha hoje já é ótico, o meu mouse naquela época era de bolinha, e ele era muito impreciso, enrolava fio de cabelo e sujeira dentro, era uma desgraça. Pensa ilustrar naquela época como era. Mesmo assim, eu treinei o quanto pude. Passava meus fins de semana fazendo ilustrações e depois de algumas semanas eu aprendi a maioria das técnicas e montei um novo portfolio, e fui bater na porta da editora de novo. E pra minha surpresa, acho que eles gostaram porque por alguns meses eu fiz ilustrações pra revista Videogame. Era uma ilustração de matéria por mês, por alguns meses só, não era grande coisa, mas eu já tava bem feliz de ter meus primeiros trabalhos publicados.

Em 94 eu parei tudo. Foquei exclusivamente em terminar o 3o colegial e conseguir passar em alguma faculdade. E eu me coloquei na obrigação de passar em alguma faculdade pública porque eu não queria que meus pais passassem os próximos 4 ou 5 anos tendo que pagar faculdade particular ou eu ter que trabalhar e estudar ao mesmo tempo pra dar conta. Eu saía de casa sei lá, 6 horas da manhã e voltava sei lá, 8 horas da noite, todos os dias e só parei depois do último vestibular do ano. Eu realmente levei isso a sério. Tinha 3 cursos que eu podia fazer naquela época: tecnólogo em processamento de dados na Fatec, um curso que na época era novo de engenharia de software ou engenharia de computação na Unicamp e bacharelado em ciência da computação na USP. No final passei nos 3 e escolhi fazer ciências da computação. Eu não tinha a mínima idéia da diferença dos 3 cursos naquela época.

Vamos pra 1995. Pra dar contexto, eu vi alguns dias atrás que vão lançar o terceiro filme da franquia Bad Boys do Michael Bay com o Will Smith e Martin Lawrence e eu dei risada de como o Martin tá gordo nesse filme. Mas o primeiro saiu em 95, pensa que foi 24 anos atrás. Nesse ano também saiu o 1o Toy Story, que foi uma revolução absurda em ser o primeiro longa metragem inteiro renderizado por computador numa época onde pouquíssimas pessoas tinham computador em casa e normalmente era alguma coisa como um 486 de 100Mhz e onde 4MB de RAM era considerado bastante.

Finalmente, na faculdade, eu podia deixar meus dias de CDF introspecto na escola pra trás e resolvi treinar pra ser mais extrovertido. Na minha época a gente chamava os novatos de bichos, acho que hoje ainda é bicho? Pra dar uma idéia de como eu tava testando meus limites, eu acabei ficando amigo de um grupo que resolveu que no 1o semestre a gente ia concorrer ao centro acadêmico contra a chapa de veteranos e nos auto entitulamos Chapa Bicho. Lembra meus desenhos de Taz na Fenasoft e meus skills de CorelDraw? Pois é, foi assim que eu fiz cartazes como esse aqui do lado. A gente era bem ativo, e não ganhamos por muito pouco. Meu primeiro semestre na USP já começou conturbado, e em paralelo a isso me engracei com a empresa junior e fiz cartazes pra eventos na faculdade como esse outro pra Oracle.

Pense que em 95 o Linux ainda não tinha muito mais que uns 4 anos de vida. Internet era absolutamente opcional ainda. Muita gente ainda nem tinha idéia direito de pra que servia Internet. Não se pode negar que eu tive o privilégio de poder cursar ciências da computação no Instituto de Matemática e Estatística da USP. Universidades como USP ou Unicamp estavam sentadas em cima do backbone da Fapesp, um dos poucos backbones que conectavam as universidades ao resto do mundo. Não era incomum algum problema nesse backbone deixar a gente ilhado e restrito a navegar só no Brasil, por exemplo.

Se formos falar de sistemas operacionais como MS-DOS com a interface gráfica do Windows 3.1 por cima, por exemplo, o stack TCP ainda era opcional. Você precisava instalar o Windows 3.1 for Workgroups ou instalar a stack TCP separadamente. Pra ter uma idéia de quão antigo estou falando, hoje em dia a gente usa redes cabeadas usando cabos 1000base-t ou 10Gbase-t, cabos twisted pair com conector RJ-45 em topologia de estrela. Antigamente eu trabalhava cabeando as salas da USP usando ethernet sobre cabo coaxial com conectores em T em rede que exigia um terminador, que é um resistor no final da rede em daisy chain. Qualquer ponto em T no meio da rede que alguém mexesse podia derrubar toda a rede local. Eu lembro que eu não sabia disso e justo na minha máquina ficava o terminador da rede. Eu não sabia o que era aquilo e desconectei. Dali a pouco desce a diretora do Centro de Computação e Eletrônica da Politécnica, o CCE, xingando "quem foi que derrubou a rede". Pois é, isso acontecia em 1995, a gente usando tecnologia de 1985.

Enfim, eu mudei de assunto aqui, foi só pra lembrar que eu já fui estagiário um dia também. Falando em ser estagiário, eu preciso deixar registrado um outro conhecimento aleatório que queimou no meu cérebro e eu nunca mais esqueci. Quem aqui sabe o que é uma gaiola de faraday? Eu fazia manutenção dos micros das salas de computação. O CCE onde eu trabalhava era lotado de periféricos e peças. Numa das manutenções eu fui trocar o HD quebrado de um computador por um novo. O que eu fiz? Tirei o HD novo do saco plástico, coloquei em cima do saco e liguei o cabo de força pra testar. Dali a pouco sobe um cheiro de queimado e eu descobri que eu fritei a placa embaixo do HD. Por que? Pelo menos naquela época o saco tinha uma redinha desenhada do lado de fora e eu não sabia pra que servia. Nesse dia eu entendi, era uma gaiola de faraday pra proteger o HD dentro. Assim como seu carro. Do lado de dentro ele é eletricamente isolado, mas do lado de fora é altamente condutor, e eu encostei a placa do HD em cima da redinha do lado de fora e liguei o HD, altamente condutor, fritou tudo na hora ... Coisas de estagiário. Pelo menos eu nunca mais esqueci.

Sobre esse estágio no CCE. Antes de entrar na USP, de vez em quando eu ajudava a BBS de um amigo meu, que era o meu vizinho da mesma rua. Lembra do meu amigo extrovertido que conseguiu os bicos na Fenasoft? Caso alguém das antigas se lembre, a BBS dele era a Medusa, o apelido online dele era Jungle. Uma das coisas que ele fazia era distribuir warez, pirataria mesmo, eu ajudava de vez em quando. Não me julguem, eu também já tive minhas épocas de não ter grana e a única forma de experimentar algum programa ou jogo era pirateando. Mesmo porque muita coisa de fora simplesmente nem chegava aqui. Não era incomum a gente usar blue boxes, que se vocês já viram algum documentário sobre Steve Wozniak deve lembrar quando ele conta a história do apito que vinha de brinde na caixa de cereais norte americanos Captain Crunch que conseguia emitir o sinal de 2600 hz que resetava a linha de trunk e com isso a gente conseguia habilitar ligação de longa distância de graça nas linhas telefônicas.

Eventualmente o povo criou software que rodava em placas de som como Soundblaster e com isso a gente conseguia fazer ligação internacional cobrando pulso de ligação local. Era só pegar o telefone, abrir a linha pra operadora, pegar o fone de ouvido ligado na Soundblaster, rodar o programa que rodava o tom e com alguma sorte ele abria a linha e agora a gente podia discar internacional. Era uma técnica de phreaking e embora nós só fôssemos os equivalentes a script kiddies hoje em dia, o povo gostava de se auto entitular phreaks. Acho que chegamos a testar black boxes também.

Enfim, a intenção era conseguir conectar em BBS de outros países como Alemanha. E além disso eles conectavam em servidores UNIX que estavam abertos na rede. Antigamente a senha hasheada ficava armazenada no arquivo passwd que era legível a um usuário guest. E o algoritmo de hash era fraco, e com brute force e rainbow tables a gente eventualmente conseguia quebrar algumas senhas e com isso tínhamos acesso a armazenamento em algum servidor. Daí a gente conseguia transferir programas pra essas contas que dávamos hijack pra fazer download com calma depois.

Lembrando que era hiper lento baixar coisas via modens de 19200 baud ou menos na época, nem estamos falando ainda de modens de 56k da última geração. Aliás, por causa desse tipo de coisa eu acho que separaram os hashes de senha nos arquivos shadow que tem permissões mais restritas. Vocês nunca se perguntaram porque existem os arquivos passwd e shadow num UNIX e Linux? O arquivo shadow já existia desde 86 mas muitos servidores UNIX rodando na época não estavam atualizados, lembrando de novo que atualizar um servidor naquela época não era só rodar um simples 'apt upgrade'.

Essa época de BBS era quando eu tinha uns 15 a 16 anos. Depois que eu entrei na faculdade ficamos sabendo que era possível eu conseguir ganhar uma conta num servidor UNIX da faculdade com acesso à internet se eu fosse trabalhar como monitor na sala do CCE na Politécnica. Internet banda larga em 95 quando a gente usava modems hiper lentos. Lógico que eu decidi tentar e essa foi a motivação pra eu ir estagiar no CCE e pra eu aprender UNIX: baixar warez de forma mais rápida. Pois é!

De qualquer forma, uma coisa leva à outra. O acesso à internet que eu tinha era logando via telnet do pczinho da sala de computação onde eu era monitor. Era acho que um mísero 386 com monitor VGA de 256 cores, mas isso não importava porque o acesso era via Telnet, ou seja, terminal texto. Uma vez logado você caía num shell, acho que era Korn Shell. Uma das chatices dos computadores da sala era que eles tinham um timeout, não lembro se era de 1 hora. O computador fazia boot remoto. Ele tinha uma placa de rede e um EPROM que no boot ele baixava o DOS e Windows do servidor então não adiantava modificar os arquivos locais porque a cada boot ele zerava tudo e baixava de novo. No DOS era carregado um programa que depois de 1 hora ou algo assim ele forçava acho que um Halt and Catch Fire ou HCF e derrubava o computador inteiro.

A primeira coisa que os estudantes mais dedicados queriam era ficar mais tempo e daí a primeira coisa que os mais espertos aprenderam a fazer foi derrubar esse programa. Como era DOS alguns estudantes mais ligeiros entenderam que se tratava de um Terminate and Stay Resident ou TSR com uma system call chamada talvez pela INT 21h, eu posso estar errado mas era algo assim. Então era só carregar qualquer outra coisa nessa interrupção e assim apagar a rotina de derrubar a máquina que era carregado no boot do DOS. A gente era insistente.

Agora eu caía no shell do UNIX. Você acha Linux hoje complicado? Você não tem idéia de como era chato um UNIX de verdade, no caso um HP-UX pra ser mais exato. Era engraçado que por alguma razão os servidores da Politécnica tinham nomes de animais como Lion, Spider. Depois resolveram colocar nomes de origem indígena, a máquina que eu tinha conta era no Amandy. Bom, pra conseguir fazer qualquer coisa útil eu precisava primeiro aprender a navegar pelo shell. E repetindo que naquela época não existia stackoverflow nem muito menos Google. Aliás, também não tinha Amazon pra comprar livros nem nada disso. A gente tinha que aprender as coisas na marra mesmo.

Como eu venho da época do MS-DOS onde basicamente a gente precisava saber comandos pra fazer qualquer coisa, o UNIX era só outra linha de comando diferente. Eu não tive o choque de ter começado numa interface gráfica e de repente cair na linha de comando. Pra mim foi o oposto, a interface gráfica que sempre foi opcional. Assim começou minha jornada pra aprender o básico, no DOS eu sabia listar as coisas com o comando 'dir' agora no UNIX era usando 'ls', no DOS eu copiava as coisas com comando 'copy' no UNIX era 'cp' e assim foi indo, um comando de cada vez. Ajudava bastante que fiquei amigo de veteranos do curso de Engenharia Elétrica da Poli, que por alguma razão eram muito mais tech savvy que o povo do IME na época. Por causa disso eu tive mais amigos na engenharia da Poli do que no meu curso no IME. Era um pessoal que desenhava circuitos integrados no café da manhã. Eu falo que cursar uma faculdade não é só pelo curso em si, mas sim por esse tipo de networking. Eu era o mais novo e o mais noobie de todos, e eu era uma esponja aprendendo tudo que conseguia deles.

Como em linha de comando não havia um navegador gráfico e como a Web no começo era muito simples, eu usava o Lynx como navegador. Hoje em dia com HTML 5, CSS 3, WebGL é impossível usar o Lynx. Eu costumo dizer que quebraram a Web hoje, mas se quiser navegar via linha de comando recomendo tentar o Browsh, que é bem mais moderno. Pra acessar meus e-mails tinha o comando 'mail' que era bem primitivo e eu nunca aprendi a usar direito mas felizmente tinha o cliente de e-mail 'pine'. Se você já usou o editor Nano no Linux de hoje em dia, ele é o clone do editor Pico, que era o que vinha no cliente Pine e que eu usava todo dia em 95. Na época eu não usava Vim mas sim o Vi original e eu lembro do desespero de entrar e não conseguir sair da primeira vez, como todo mundo. Aprender o dois pontos "q" foi uma revolução! Aliás, uma versão alternativa mais moderna do que o Pine hoje parece ser o Aerc, ou você pode usar o fetchmail pra popular o maildir, ou eu "acho" que tanto o Pine quanto o Mutt suportam IMAP pra se conectar em servidores de e-mail mais modernos. Antigamente o protocolo era o POP3 e procmail.

Na sequência, aprendi a usar a Usenet, um pouco de Gopher, IRC e assim meu arsenal começou a aumentar. Mas preciso confessar que eu gastava a maior parte das minhas horas perdendo tempo em chat de IRC mesmo. E eu não estou falando de mIRC no Windows, mas do cliente de IRC original do UNIX na linha de comando. A gente fuçava mensagens na Usenet, descobria servidores onde o povo deixava arquivos, baixava bastante coisa via FTP. Tinha bastante coisa gratuita não-warez no FTP da Unicamp na época, foi onde eu baixei o beta do Java que ficava numa pasta chamada Oak, que depois fui entender que era o codenome do Java antes do 1.0.

Os protocolos de internet na época eram muito simples, a maioria baseado em texto e estamos falando de uma época anterior a encriptação como TLS, então hoje usamos SSH mas antigamente era só Telnet. Hoje usamos SCP mas antigamente era FTP e assim por diante. Além de FTP, muitos programinhas e mesmo imagens e outros arquivos binários eram trafegados no corpo das próprias imagens na Usenet ou até IRC. Pense google groups ou stackoverflow de hoje. Porém na usenet só trafegava texto, então como trafegar binários? Assim eu aprendi sobre unix to unix copy protocol ou UUCP e as ferramentas uuencode e uudecode, que transformava binários de 8-bits em textos de 7-bits. O equivalente hoje seria o encode e decode de Base64. Finalmente o tráfego de warez estava começando a acontecer agora que eu sabia como me comunicar, onde procurar, e como trafegar os binários.

Mesmo em banda larga às vezes a fonte vinha de servidores lentos. E às vezes os arquivos eram quebrados em múltiplas mensagens na usenet. Daí a gente aprendeu a deixar os downloads via FTP rodando em background e com isso eu aprendi que UNIX suporta o conceito de jobs. Rodar o comando com o "E" comercial no final ou fazendo Ctrl + Z e aprendi a usar comandos como 'bg' e 'jobs' e 'nohup'. Eu não me recordo de ter usado comandos como 'disown', eu não entendia muito bem o conceito de signals ainda, como o HUP de hang up quando o terminal desconecta, fui aprendendo aos poucos. De qualquer forma, assim eu podia deixar as coisas baixando em background, deslogar da minha sessão, ir pra casa e no dia seguinte, se nada deu errado, pegar meus arquivos. Vocês podem ver que eu fui aprendendo a usar UNIX com a motivação de baixar pirataria, um motivo super nobre!

Não era comum na época consumir as coisas online como fazemos com YouTube ou Spotify. A banda era limitada, a velocidade era baixa, então a gente baixava tudo pra poder consumir depois. Vou dizer que a essa altura eu já estava menos interessado nos warez e mais engajado nas salas de chat de IRC e tentando entender melhor como a Web funcionava. Interessante que eu usava Windows 3.1 e Windows NT já naquela época, a gente tinha navegadores como Netscape Navigator Gold onde eu aprendi a editar HTML, FrontPage da Microsoft, editores como o HotDog eu acho, mas pra coisas como IRC, FTP, Usenet a gente usava terminal mesmo. A internet era mais devagar nas máquinas locais e mais rápido do servidor, então era um incentivo pra continuar no terminal. Eu finalmente comecei a me sentir confortável com essa tal de Internet e suas tecnologias de base.

E esse foi um pequeno resumo de alguns episódios que eu lembro entre 1990 e 1995. O episódio não tem nenhum objetivo muito educativo a não ser contar minha história mesmo. Pode ser que eu esteja sendo leviano mas até hoje eu nunca atrelo aprender alguma coisa com nenhum objetivo muito grandioso. Eu nunca penso "vou aprender X porque isso pode ser mais lucrativo no futuro" ou "quero aprender Y porque parece que vai me levar pra uma carreira mais promissora" ou qualquer coisa planejada assim. Todas as memórias que eu tenho de aprender alguma coisa sempre tiveram objetivos super banais, como vocês puderam ver. Se resumir a última metade do vídeo você pode dizer que eu aprendi UNIX pra baixar pirataria mais rápido. Só que tudo que eu aprendi: copiando apostila de programação, sendo estagiário do meu tio, apresentando games na Fenasoft, fazendo ilustrações pra revista videogame, aprendendo as tecnologias de internet pra baixar warez, tudo me levou a aprender coisas como dBase, Clipper, CorelDraw, UNIX e aprender a falar em público.

Eu falei no episódio de Procure o que Ama só que não, que eu gosto do meu esforço, e é isso que eu quero dizer. Naquela época dos 13 aos 18 anos eu sequer tinha essa noção de que a gente devia procurar alguma coisa que ama. Eu só fui fazendo, sendo curioso. Aliás, a definição de hacker pra mim é uma pessoa curiosa com tecnologia. Uma coisa foi levando a outra, e sem querer se passaram 5 anos que eu fui aprendendo um monte de coisas que se tornariam importantes nos anos seguintes.

Se eu ficasse toda hora pensando "ah, não vou perder tempo com isso porque não sei se vai ser útil daqui 5 anos" eu ia ficar estagnado o tempo todo só planejando, planejando e nunca de fato fazendo. Esquece, nessa fase da vida é inútil ficar planejando, a gente é jovem demais pra ter noção real de qualquer coisa. E eu estou parando a história exatamente antes de começar a primeira bolha da internet, nesse ano eu ainda ia ver o lançamento de coisas como Java 1.0, Delphi 1.0, Flash 1.0 no ano seguinte, .NET nem existia ainda, todo mundo ainda usava Perl e não Python, UNIX e não Linux, PHP ainda não tinha começado.

Esse foi um breve resumo dos meus primeiros 5 anos, eu sei que não é um conhecimento geral mas eu queria deixar registrado essa fase pra posteridade, espero que tenha ajudado a dar alguma perspectiva pra alguns de vocês. E vocês, como foram os seus primeiros 5 anos? Não deixem de compartilhar nos comentários abaixo, se curtiram mandem um joinha, não deixem de assinar o canal e clicar no sininho pra não perder os próximos episódios. A gente se vê, até mais!


[Akitando] #63 - Não Terceirize suas Decisões! | A Lição MAIS Importante da sua Vida

Description

Faz anos que eu recebo o mesmo tipo de pergunta toda vez, mais agora por causa do canal.

“Akita, o que você recomenda que eu faça na minha carreira?”

“Akita, o que eu deveria aprender primeiro?”

“Akita, tenho família pra criar, como eu mudo de profissão sem arriscar eles?”

E por aí vai.

Hoje eu vou meio que não responder e responder ao mesmo tempo. Mas eu quero ir além e explorar como você mesmo deveria pensar sobre essas perguntas pra chegar na melhor resposta pro seu caso.

E ao mesmo tempo quero tentar explicar como você deveria pensar pra não ser enganado por charlatães que te oferecem respostas atraentes e fáceis.

== PARA OS VIDEÓGRAFOS

Se você assistiu meu video anterior onde eu explico sobre as configurações do meu equipamento e cenário, lembram que o áudio estava ruim, porque eu testei gravar ligando o microfone direto na câmera.

Não tem jeito, um gravador bom separado tem qualidade melhor. Então eu liguei o Rode Wireless Go no gravador Tascan pra poder ter microfone de lapela sem fio.

E na configuração da minha Canon Rebel T7i eu subi o shutter speed de 30 pra 60 como recomendaram, mantive o aperture em f/1.4 e subi o ISO de 100 pra 200 e acho que ficou melhor.

O que acharam?

SCRIPT

Olá pessoal, Fabio Akita

Finalmente estou de volta, este mês passei 2 semanas fora, primeiro em Miami, depois nos meus escritórios do Rio Grande do Sul semana passada inteira. E daqui 2 semanas vou viajar de novo. Essas últimas semanas estão corridas, em novembro devo voltar ao normal, e ainda tá na minha lista pra fazer episódios mais técnicos, mas por agora resolvi fazer um Rant de novo. Não chega a ser Rated R, mas acho que tá perto.

O tema deste episódio é sobre tomada de decisões, em particular sobre dois tipos de perguntas que me fazem o tempo todo. Que decisões eu devo tomar pra minha carreira. E que decisões eu devo tomar pros meus projetos ou empresa. A resposta mais simples que eu posso dar é: pare de terceirizar suas decisões. Não terceirize decisões importantes da sua vida pra ninguém. E sobre as partes técnicas, o resumo é: a menos que você seja engenheiro de uma tech startup com mais de dezenas de desenvolvedores e com valuation de milhões de dólares, para de prestar atenção na maioria das palestras e artigos de engenheiros de Facebook, Google, Nubank, Netflix, Amazon e similares.

Essas duas questões estão interligadas e quanto mais cedo você aprender a lidar com isso melhor vai ser pra você. Eu quero tentar explicar porque você acha tão difícil tomar decisões, porque você acha tão difícil saber o que estudar, e porque você toma muitas decisões erradas que não precisaria tomar. E porque sua ânsia em não querer perder tempo nem dinheiro é justamente o que vai te trazer mais prejuízo.

Desde que eu comecei a blogar, palestrar e principalmente agora que abri o canal, eu tenho recebido um número preocupante dos mesmos tipos de pergunta. Faz mais de uma década que ouço as mesmas indagações. E imagino que diversos outros palestrantes e “influencers” recebam perguntas similares. Um dos tipos de pergunta tem a ver com o que a pessoa, como iniciante, deveria aprender primeiro, mais especificamente, que plataforma eu deveria escolher, que linguagem é a melhor, que ferramentas eu deveria investir. Se assistiu os outros vídeos do meu canal e ainda não entendeu a resposta é simples: essa resposta não existe. Só isso. Eu prefiro perguntas mais objetivas, coisas como “Akita, se eu quiser usar Ruby pra fazer aplicativo desktop, rola?” Aí eu consigo responder objetivamente: “Não”.

Mas perguntas que claramente tem a expectativa de justificar uma escolha, ou pior, terceirizar a decisão da sua carreira pra mim ou qualquer outro, não faz nenhum sentido. Por que você quer terceirizar as decisões da sua vida pra outra pessoa? Não faça isso. Só porque você vê alguém como eu ou qualquer outra pessoa em cima de um palco, isso não nos torna oniscientes, eu não te conheço, não sei que caminho você trilhou até agora, que dificuldades você teve, eu não sei quais seus objetivos de vida, se é que você tem objetivos. Nem eu, nem ninguém, poderia te dizer o que fazer sem conhecimento da sua história e dos seus objetivos.

Eu falei isso em outros vídeos e palestras quando falo de faculdade e como tem conclusões idiotas do tipo “ah, não vou fazer faculdade porque olha lá o Bill Gates ou o Mark Zuckerberg, eles não terminaram a faculdade e se tornaram bilionários”. Que conclusão imbecil, sério, a menos que seja dito na brincadeira, mas se a pessoa realmente pensa dessa forma a próxima coisa óbvia a se dizer é: conta a história direito, primeiro você entra em Harvard, se você conseguir entrar em Harvard e ficar lá uns 2 ou 3 anos, aí sim, pode parar pra pensar em desistir. Agora sair do seu EAD de esquina achando que vai ser o Bill Gates é o cúmulo da alucinação.

Aliás, eu nunca leio biografias, eu não tenho paciência e nem curiosidade de saber sobre a vida dos outros. Eu assisto filmes de biótipos, sabendo que é ficção. Todo filme que começa com “baseado numa história real” é exatamente o que ele quer dizer: a base é real, mas a história toda é uma ficção, cuidadosamente desenhada pra estimular suas emoções por duas horas. Só isso. Todo mundo tem uma história de drama na vida. Algumas mais dramáticas que outras. Mas isso não as torna oniscientes e muito menos onipotentes pra de repente terem sabedoria universal e a resposta pros problemas de tudo e de todos. Sério, pára pra pensar por dois segundos.

Se você realmente leva a sério sua carreira e sua vida em geral fica a dica: pare de terceirizar as decisões da sua vida pra “influencers”. Imbecis como eu aqui cagando regra não vai te ajudar. Eu sempre conto histórias e faço análises colocando contexto. Meu objetivo não é te dar respostas, é tentar dar dicas de como você acha as próprias respostas. Por isso eu falo tanto em “Aprendendo a Aprender”. O processo é simples na real: você nunca vai ter a resposta correta pra tudo, desista de tentar achar. Então como faz? Você estuda, você usa sua experiência, você erra e aprende com seus erros, quando dá você observa os erros dos outros e tenta não errar igual. Mas mais importante: assuma que você vai errar, e assuma que você vai ter prejuízos e ponto final. O problema não é acertar sempre, é como você se recupera depois de um erro. E muito do que você considera “erro” hoje pode ser útil mais pra frente, você só não sabe disso ainda.

Via de regra, quanto mais jovem você for, mais devia usar seu tempo pra estudar. Agora a questão é: estudar o que? Não importa. Vocês viram meu vídeo dos meus primeiros 5 anos? Tecnicamente, nada do que eu estudei naquela época é prático hoje, mas tudo que eu estudei valeu a pena na época. Principalmente: o fato de eu estar praticando o ato de estudar. Estudar é uma arte, você aprimora a técnica e com o tempo se torna mais eficiente e mais rápido em estudar. O que você estuda é menos importante do que “como” você estuda.

Um exemplo pequeno: antigamente eu achava que precisava ler os livros técnicos do começo ao fim. Levava um puta tempo. Hoje em dia eu raramente leio livro técnico do começo ao fim. Eu leio os primeiros capítulos e, se eu preciso, eu volto nos capítulos do meio à medida que vou precisando, se precisar. Se eu nunca voltar, é porque o assunto não era útil pra mim, e eu pulo pra outro assunto. Eu digo isso hoje, que eu tenho 42 anos. Com 20 anos, você deveria mesmo ler do começo ao fim. Não tente queimar passos, é que nem você que acabou de aprender a andar na bicicleta com rodinhas achar que vai fazer bicicross pulando rampa. Não vai.

Se você está pra começar a faculdade, entenda que nos últimos 10 anos você aprendeu a estudar do jeito errado. Eu percebi isso só quando entrei na faculdade também. Por mais de 10 anos da sua vida você aprendeu a seguir um currículo. E você se condicionou a estudar o mínimo necessário e só pouco antes dos trabalhos ou provas. E as frequências das provas é sempre a mesma, provas bimestrais. E você sempre sabia o que estudar, porque já estava pré-definido. Todo mundo estuda o mesmo material, você nunca teve que escolher o que estudar.

Daí você entra na faculdade, e é mais ou menos a mesma coisa. Todo curso tem material pré-definido. Você tem as provas em datas pré-definidas. Talvez uma das poucas atividades que quebrem a rotina seja o Trabalho de Conclusão de Curso, o temido TCC. Que a maioria faz só na última hora mesmo que eu sei. Quem faz iniciação científica talvez tenha tido mais treinamento, mas no geral, você continua seguindo um currículo.

Parem pra pensar nisso: se você nunca repetiu de ano, você tem 21 ou 22 anos, recém formado da faculdade. E você passou uns 14 ou 15 anos da sua vida, ou seja, literalmente sua vida inteira seguindo uma receita pré-definida. É por isso que muita gente reclama quando entra no mercado de trabalho que a faculdade não preparou ele direito. Eu nem vou entrar no mérito da qualidade e da metodologia da educação moderna, e também não adianta chorar sobre o leite derramado. Só aceite que até o fim da faculdade, uma pessoa mediana só aprendeu a tirar 5 pra passar de ano e de repente não tem mais prova bimestral, não tem mais currículo, não tem mais professor “enchendo seu saco”. A partir de agora, todo dia é uma prova. Você só andou de bicicleta de rodinhas e do dia pra noite vai ter que aprender a fazer bicicross e saltar uma rampa por dia.

Eu não sei dizer exatamente quando, mas eu me dei conta disso no começo da faculdade. E tive a oportunidade de ir aprendendo a tirar as rodinhas da bicicleta aos poucos, caindo com mais tempo pra me recuperar, e foi um dos motivos de porque eu saí antes de terminar a faculdade, quando eu me vi já conseguindo me equilibrar na bicicleta sem rodinhas e tentando saltar a rampa antes.

No nosso mercado de tecnologia isso é difícil porque ele ainda não é maduro pra ser bem estruturado. Isso é ao mesmo tempo ruim e bom, bom porque significa que quem arrisca mais pode conseguir uma recompensa maior. Vamos deixar uma coisa muito clara: meu cérebro não é muito diferente do seu não. Não tem nada intrinsecamente codificado aqui que me faz automaticamente melhor que você. Eu aos 20 anos não sou diferente de você aí me assistindo, com seus 20. Na verdade eu diria que você aí com 20 muito provavelmente sabe mais e tem mais habilidades do que eu aos 20. Comece considerando o seguinte: você teve internet uma parte considerável da sua vida já, eu só fui ter acesso à internet, principalmente de noite, lá pros 17 ou 18 anos.

Eu nunca perguntei o que eu devia fazer pra ninguém. Claro, eu ouço, eu observo, eu faço perguntas objetivas, mas eu nunca tive uma gota de vontade de perguntar pros influencers da minha época o que eu deveria fazer. Aliás, eu nem lembro quem eram os influencers da minha época. Nunca perguntei pra ninguém se o que eu estou fazendo está “certo” ou não. E isso é outra razão de porque eu digo que ninguém deve me copiar. As pessoas são diferentes, eu sou do tipo que faz as coisas quieto e sem perguntar. Eu não faço enquete, não tenho paciência. Quando vêem eu já fiz.

Em termos de carreira eu nunca tive ídolos na minha área. Eu jamais segui e jamais seguiria ninguém cegamente. Todas as decisões importantes são minhas, certas ou erradas. Se eu tomar a decisão certa, 100% do mérito é meu. Se eu tomar a decisão errada, 100% do prejuízo é meu também. Quando um adulto fica inventando desculpa pelos prejuízos de suas más decisões e dizendo “ah, eu segui o que o fulano falou, mas ele errou e eu me fodi, esse cara é um bosta”. Não filho, o bosta é você. Ou então chorando coisas do tipo “ah, eu costumava admirar essa pessoa, mas o que ele falou hoje eu discordo, eu esperava mais dele”. De novo, o bosta é você, até agora você usou o que esse fulano fez ou falou e foi bom pra você, mas agora sua visão pequena de mundo entrou no caminho e de repente tudo que esse fulano representa pra você é nada. Isso diz mais sobre você do que sobre esse fulano.

Não existe e jamais vai existir pessoas perfeitas. Eu não faço nenhuma questão de concordar 100% com qualquer pessoa, de qualquer era. Eu aproveito os 20% que certas pessoas produziram e que são úteis pra mim, e ignoro os 80% que eu discordo. Não tenho vontade nenhuma de perder tempo fazendo linchamento público falando mal das partes que discordo só pra parecer ser o bonzão do Twitter. Na verdade é o oposto: eu sempre considero que todo mundo que se gaba fazendo linchamento público está se refletindo em quem está atacando. Esse tipo de pessoa eu só bloqueio. Eu tenho coisas mais úteis pra fazer com meu tempo do que tentar mudar a opinião xiita dos outros, prefiro gastar meu tempo jogando videogame. Aliás, outra dica aqui: se quem você segue gasta muito tempo falando mal dos outros e reclamando da vida publicamente, pare de seguir essa pessoa, você está perdendo tempo.

Eu digo isso porque eu vejo que a maioria das pessoas sente a tal pressão social de ter que fazer parte de algum grupo. Quando você se vê não se encaixando em algum grupo, você se sente o errado. No meu caso eu tive outra vantagem: eu sou descendente de japonês, e no meu caso, desde criança, eu nunca me encaixei em nenhum grupo e em muitos casos ainda as pessoas faziam questão de me deixar de fora dos grupos mesmo. O que eu ia fazer? Ficar me sentindo vítima da sociedade? Nah, eu simplesmente me acostumei a não fazer parte de nenhum grupo. Eu sou anti social, eu sempre me sinto melhor sozinho e de bônus não sinto ansiedade de estar indo contra algum grupo ou de não fazer parte de nenhum.

Uma coisa que eu repito que eu adoro na matemática é o seguinte. 2 mais 2 é 4, ponto final, do domínio dos inteiros pra imagem dos inteiros, é 4. Não importa se um milhão de pessoas derem like num tweet de um influencer dizendo que é 5. Foda-se, continua sendo 4 e ponto final. Quantidade de likes não torna uma coisa verdade. Se falou merda, continua sendo merda, não importa quantas milhares de pessoas concordem, pra mim é só milhares de pessoas repetindo merda. Zero pressão e não afeta minhas decisões.

Além dessa pressão social e tirando o fato que se você é jovem, nunca teve que decidir muita coisa. De fato, hoje em dia você tem acesso a informação demais, você tem escolhas demais, e isso deve ser bem estressante. É uma das razões de porque eu digo que o mundo hoje é melhor do que 20 anos atrás, ou 50 anos atrás. O mundo antigamente parecia mais “fácil” pros mais velhos porque não tinha muito o que escolher, não tinha nem informação suficiente. Considere que hoje você TEM opções pra escolher. Você só não sabe o que fazer com elas. Não é a toa que tem tanto psicólogo e tanto coaching hoje em dia.

Mas isso vai mesmo te deixar mais e mais frustrado, você acha que tá estagnado. Você não consegue enxergar o tal “futuro brilhante” do século XXI que te prometeram. Eu sei que estou sendo duro, mas preciso ser. Pare de terceirizar as decisões que você tem que tomar. Não faz sentido você me mandar 2 parágrafos de texto no direct message do Insta, do Twitter, do Face, do LinkedIn pra mim ou qualquer outro influencer e achar que vai ter uma resposta certa pra uma pergunta tão subjetiva. E o pior disso: algumas pessoas realmente acham que conseguem dar a resposta certa: suspeite dessa pessoa, ela provavelmente está tentando te vender alguma coisa.

Eu venho tentando responder com ponderação quando me mandam essas mensagens, mas vou dizer que não me sinto bem respondendo e cada vez mais venho postergando dar uma resposta. Porque as respostas nunca vão ser completas o suficiente. Mas mais importante: eu não tenho skin in the game. Se o que eu sugerir der errado, eu não vou sentir, você é que vai. Sempre se lembre desse conceito e jamais acredite em ninguém que te ofereça respostas fáceis pra esse tipo de pergunta, ele está querendo vender livros, cursos ou, deus me livre, coaching ….

Então como você deve fazer? Se você já tem 20 anos, não se preocupe. Você tem tempo de sobra pra errar. Erre rápido, assuma que errou, mude de direção, repita. Começou a ler um livro e está achando ele ruim? Pare de ler. Simples assim. O que a maioria das pessoas tem dificuldade em fazer é assumir custo perdido, e eu falei disso no meu video sobre prioridades. Você já leu vinte capítulos agora acha que precisa ler os próximos 50. Você não quer assumir o tempo que perdeu lendo esses vinte capítulos, então acha que precisa ler os próximos 50. Mas agora você perdeu o tempo de 70 capítulos, só isso. O mesmo vale pra cursos. O mesmo vale pra qualquer outra decisão.

Assuma que você nunca foi treinado pra tomar decisões. Assuma de uma vez que é impossível tomar decisões certas o tempo todo, prepare-se pra assumir algum prejuízo. E não tente achar que outra pessoa vai tomar uma decisão melhor que você, se isso acontecer eu diria que foi mais obra da sorte do que qualquer outra coisa. Especialmente se não foi uma conversa e sim uma resposta fácil. A única pessoa que pode de fato opinar com alguma propriedade é alguém que tem skin in the game: ou seja que vai cortar da própria pele se der errado. Se a pessoa pra quem você está perguntando não tem skin in the game, jogar cara ou coroa vai ter o mesmo resultado. Opinião é que nem bunda, todo mundo tem um.

Tudo é uma aposta. É impossível acertar todas. A melhor estratégia é ser anti-frágil ou seja, fazer apostas que se você perder, perde pouco, mas se ganhar pode ganhar muito. Não existe apostar sem risco de perder nada. Qualquer um que te prometa “ganhos garantidos” é um charlatão e você está entrando numa pirâmide. Vou repetir: não existe ganho “Garantido”. Eu diria até que tecnicamente o charlatão está dentro do seu direito. Quando ele escreve coisas do tipo “quer ser rico como eu” ou “ganho garantido” ele está explicitamente dizendo “ow, seu trouxa, me faça rico, me dê seu dinheiro”. Se as pessoas dão o dinheiro sem coerção, foi uma escolha. Simples assim.

Como eu disse no começo: a culpa de uma decisão errada é de quem toma a decisão errada. Pare de achar que existe almoço de graça, não existe. Pô mas as pessoas não deviam enganar as outras. Não deviam mesmo, é um incentivo econômico, enquanto você fica caindo, eles vão continuar. Se ninguém mais acreditar em conto de fada, eles deixam de existir. Olha só que mágico. Mas como isso nunca vai acontecer, em qualquer era da história sempre vai existir alguém que vai acordar de manhã pra procurar o trouxa da vez, só se certifique que não é você. Como se diria em poker, se você não sabe quem é o pato da mesa, o pato provavelmente é você.

Falando sobre tecnologia em particular, vou ser mais breve agora: pare de idolatrar palestras e artigos de grandes empresas. Vamos praticar o pensamento crítico: quando um engenheiro do Netflix vem, com toda boa intenção do mundo, explicar como eles resolveram o problema deles de suportar os mais de 100 milhões de assinantes, ele está dizendo exatamente isso: se você for o Netflix, tiver 100 milhões de assinantes, essa solução que ele está apresentando tem grandes chances de funcionar. Ponto.

Agora, a culpa é sua de decidir colocar a solução do Netflix no seu projeto que tem 2 desenvolvedores e zero clientes. Você pensa “se o netflix está usando e suporta tudo isso, pra mim vai ser melhor ainda”. Sério? Eu não consigo entender porque alguém pensaria dessa forma. Lembra da analogia da bicicleta? Você nem subiu na de rodinhas ainda, mas aí você assiste uma palestra, digamos, de um Lance Amstrong e ele fala que a bicicleta favorita dele é o Parlee Z-Zero que é uma mountain bike de mais de 8 mil dólares e você acha que se comprar essa bicicleta vai virar um recordista de endurance?

Eu espero que ninguém me assistindo agora pense assim, porém eu tenho certeza que muitos de vocês vêem palestras de engenheiros do Facebook, do Netflix, do Google, de empresa famosinha tipo Nubank, e toma decisões assim. Eu deveria escolher Clojure porque o Nubank usa. Eu deveria escolher React porque o Facebook usa, eu deveria escolher Go porque o Google usa. Da mesma forma que não tem nada de errado num Parlee Z-Zero, sério, é uma bicicleta excepcional, também não tem nada de errado num React ou Go ou Clojure ou qualquer outra tecnologia que eles falam. O errado é sua decisão.

Como eu disse no começo: se você é o CTO de uma tech startup que vale milhões, você nem precisa deste vídeo. Mas estou assumindo que a maioria me assistindo não está nessa posição. É a mesma coisa sobre os Squads do Spotify, se você está tentando copiar sem entender, você é um péssimo tomador de decisões. De novo, aqui está você tentando terceirizar sua decisão. Você é simplesmente preguiçoso, porque você não quer ter o trabalho de fazer a pesquisa, fazer provas de conceito e experimentos, testar as hipóteses pro seu contexto, errar e tentar de novo, e se contenta em dizer “se der errado, eu não ia fazer melhor, se a solução de um Spotify não funcionar, nada mais vai funcionar, paciência”. Puta que pariu, cadê a “ciência” em ciências da computação?

Contexto, contexto, contexto. Você tá no seu dia 1 ainda. O Netflix foi lançado em 97, provavelmente quase nenhum código que eles escreveram no dia 1 deles existe hoje. O Google foi fundado em 98, e de novo, quase nenhum código desse dia 1 existe hoje. O Facebook foi fundado em 2004, tão entendendo? Não se compare com eles hoje, se compare com eles no dia 1 deles. E no seu dia 1 você vai fazer merda, assume isso de uma vez. O código que você escrever hoje, se você for bem sucedido, vai ser uma merda pra crescer. Vai mesmo, mas se preocupe com isso quando você sobreviver e de fato estiver crescendo. No dia 1 sua preocupação não é se você tá usando kubernetes ou não. No dia 1 sua preocupação não é usar GraphQL. No dia 1 sua preocupação não é CQRS. No dia 1 sua preocupação é durar até o dia 2.

Pra não ficar mal entendido, obviamente eu não sou contra palestra de empresa grande não. Acho excelente eles compartilharem conhecimento. Eles estão explicando uma solução dentro do contexto deles, com recursos, infraestrutura e muita escala. Se um dia você chegar perto de uma fração do tamanho deles, deve valer a pena. O errado é seu processo de tomada de decisão, que nem começou a codar o produto ainda, e se baseia em “deixa eu ver se o Google usa” ou sei lá “se a Nasa usa” daí eu posso usar. Vai se fuder, Nasa? Você vai mandar foguete pra Marte?

Eu prefiro ser anti-frágil, eu prefiro saber as tecnologias populares pro curto prazo e fico aprendendo tecnologias que vejo potencial mas ninguém usa, ninguém fala muito a respeito, e não é tão fácil de saber. Assistiram minha história sobre Ruby on Rails? Em 2005 ninguém tava muito interessado, eu tive sorte e minha aposta deu certo. Hoje você usa produtos Rails todo dia como GitHub. Teve gente que entrou na onda de mobile quando devia: em 2010 e surfou. Tudo tem uma curva de adoção em S, começo devagar, adoção exponencial, e desaceleração. Daí vem a próxima curva. Nada dura pra sempre, e eu sempre aposto em 10 anos. Se eu tenho várias opções, mesmo que eu erre uma, se eu acertar as outras, meu prejuízo é pequeno. Anti-frágil não é prejuízo zero, é pouco prejuízo. Tente acertar 100% e você provavelmente vai entrar no caminho de perder tudo, é o cara que coloca todos os ovos numa única cesta apostando que achou a cesta mágica do IPO de unicórnio. Bullshit.

Lembram meu vídeo sobre Dimensão do Tempo? Se você tem 20 anos, eu falar em 10 anos é basicamente sua vida inteira. Você ainda não tem noção do que isso significa, pior, parece que é bastante tempo. Mas não é, 10 anos passa muito rápido. Quando você menos esperar já vai estar com 30 e se perguntando porque você ainda está frustrado e continua tão difícil decidir as coisas. E aqui vai minha resposta: não é porque o mundo está pior ou o “sistema não funciona”, é porque você não pára de terceirizar suas decisões.

Por último, parem de colocar expectativas irreais nas pessoas. Eu não sei tudo, façam a análise óbvia. Por exemplo, tem gente que me pergunta que curso eu recomendo. Eu não recomendo nenhum, primeiro porque eu não faço propaganda de graça. Segundo, eu já não faço mais cursos faz anos, portanto qualquer opinião que eu der vai ser baseado no que eu ouvi dos outros ou se eu conheço os donos ou quem faz o curso. Pode ser uma resposta razoável, mas não é o que você pensa. Qualquer um que te responda só pode dizer sobre o curso que ele fez, se gostou ou não, e só. Ou então se está ajudando o amigo que faz o curso. De novo, não é o que você quer saber. Então o que você faz? Pesquisa, vê os reviews, procura sobre a reputação, e tenta fazer. Se não gostar, desiste e faz outro, é a única forma objetiva de lidar com essas coisas. Agora, meu medo é eu responder “ah, ouvi dizer que esse curso parece bom”, daí a pessoa ouve e faz até o fim “porque o Akita recomendou”. Caraca, não né.

Repetindo o que eu faço: eu não tenho muito interesse na opinião dos outros não. Claro que eu ouço, de vez em quando, mas é só uma referência. Na verdade eu sou chato, eu sou do contra por padrão. Eu fui consultor por muitos anos, minha profissão era achar defeito nas coisas. Então qualquer um que me venha com uma opinião vai ter que se esforçar muito pra me convencer. Se conseguir me convencer, só ganhou minha atenção. Agora, da minha atenção pra ganhar meu interesse são outros quinhentos.

Eu não consigo entender porque tem gente que procura tanto opinião, por exemplo, de artistas sobre coisas que não tem a ver com arte. Da mesma forma que eu não imaginaria porque alguém iria querer ouvir minha opinião sobre coisas que não tem a ver com tecnologia e cultura geek. Se eu der qualquer opinião aqui sobre gastronomia, ou sei lá, política, duvidem muito de mim, eu não gastei anos estudando pra saber. Pra começar porque eu tenho zero interesse em política.

Eu evito ansiedade desnecessária assim: eu ativamente me esforço pra saber só o mínimo sobre coisas fora das minhas áreas de atuação. Eu me interesso em princípios e fatos, opiniões são opcionais. Se os princípios fazem sentido, e se os fatos batem, de novo, talvez tenha minha atenção. Eu não assisto TV faz anos, nem assino. Não leio portais de notícias e muito menos jornais. Shows de celebridades então, nem sei quem são os de hoje em dia. Eu tenho mais gente bloqueada e silenciada no meu Twitter do que gente que eu realmente sigo. Nada disso afeta meu lucro no fim do dia.

Se você se acostuma demais a terceirizar suas decisões, e se você se acostuma a reclamar demais em rede social, e toda vez alguém aparece pra concordar e validar sua opinião, o que é você? Uma pessoa sem controle da própria vida, buscando validação de um grupo semelhante, reclamando do seu celular importado, com internet banda larga, tomando café gourmet, do sofá de casa, com netflix ligado na sua frente, muitos inclusive morando fora do país, sobre como a vida é difícil e você é uma vítima da sociedade. Ah vá.

O resumo deste video já está logo na introdução, mas eu realmente queria gastar o tempo pra elaborar esse pensamento um pouco porque eu realmente acho que se entendido corretamente é uma das coisas mais importantes que vai fazer diferença pra você. Quanto mais cedo você entender isso de verdade, melhor. Assuma suas decisões. E se você aprendeu alguma coisa nos meus materiais e teve bons resultados, o mérito não é meu, o mérito é seu que escolheu e decidiu usar de alguma forma.

Enfim, foi um longo rant só pra dizer pra pararem de perguntar o que vocês devem fazer com suas vidas. Se vocês tem dúvidas objetivas, técnicas, mensuráveis, então não deixem de mandar nos comentários abaixo, se curtiram o vídeo deixem um joinha, assinem o canal e não deixem de clicar no sininho pra não perder os próximos episódios. Se conhecem alguém que tem esse mal de terceirizar as decisões pros outros, compartilhem esse video. A gente se vê, até mais!

05:45 - 5 anos 08:54 - faculdade 10:33 - manifesto 13:49 - mundo melhor 15:47 - prioridades 20:21 - squads 22:40 - rails 23:27 - tempo

Faz anos que eu recebo o mesmo tipo de pergunta toda vez, mais agora por causa do canal.

“Akita, o que você recomenda que eu faça na minha carreira?”

“Akita, o que eu deveria aprender primeiro?”

“Akita, tenho família pra criar, como eu mudo de profissão sem arriscar eles?”

E por aí vai.

Hoje eu vou meio que não responder e responder ao mesmo tempo. Mas eu quero ir além e explorar como você mesmo deveria pensar sobre essas perguntas pra chegar na melhor resposta pro seu caso.

E ao mesmo tempo quero tentar explicar como você deveria pensar pra não ser enganado por charlatães que te oferecem respostas atraentes e fáceis.

== PARA OS VIDEÓGRAFOS

Se você assistiu meu video anterior onde eu explico sobre as configurações do meu equipamento e cenário, lembram que o áudio estava ruim, porque eu testei gravar ligando o microfone direto na câmera.

Não tem jeito, um gravador bom separado tem qualidade melhor. Então eu liguei o Rode Wireless Go no gravador Tascan pra poder ter microfone de lapela sem fio.

E na configuração da minha Canon Rebel T7i eu subi o shutter speed de 30 pra 60 como recomendaram, mantive o aperture em f/1.4 e subi o ISO de 100 pra 200 e acho que ficou melhor.

O que acharam?


Code With Jason

My Git aliases

It pains me every time I see someone fully type out git commit. You can save a whole bunch of typing and cognitive energy by aliasing common git commands to shorter versions.

Here are my Git aliases if you’d like to use them. I use Zsh so I put these aliases in my ~/.zshrc file.

alias gs='git status '
alias ga='git add '
alias gb='git branch '
alias gc='git commit'
alias gca='git commit -a'
alias gd='git diff'
alias gdc='git diff --cached'
alias go='git checkout '
alias gk='gitk --all&'
alias gx='gitx --all'
alias gpom='git push origin master'

The post My Git aliases appeared first on Code with Jason.


RubyGems Blog

3.0.8 Released

RubyGems 3.0.8 includes bug fixes.

To update to the latest RubyGems you can run:

gem update --system

If you need to upgrade or downgrade please follow the how to upgrade/downgrade RubyGems instructions. To install RubyGems by hand see the Download RubyGems page.

Bug fixes:

  • Gem::Specification#to_ruby needs OpenSSL. Pull request #2937 by Nobuyoshi Nakada.

SHA256 Checksums:

  • rubygems-3.0.8.tgz
    a462d8f9860a17f8dc1a713c51ce3888b3b4c4897e2896426d67a90628277632
  • rubygems-3.0.8.zip
    bf2a468704e32b397be1028671bd6572bd1c6affa024b95b67f760f5181f0277
  • rubygems-update-3.0.8.gem
    6b02065e13df8cb365ece9726ea542ba5ea3527d40b58d21bf63e6cf998248d0

Saeloun

Rails has added support for automatic database connection switching from primary to the replica

We discussed in one of our recent blog post, on how to connect to multiple databases and perform manual connection switching. Today, we are going to discuss how Rails can help you automatically switch connections from the primary to the replica.

Let’s once again revisit how the database.yml will look like for the primary and the read replica configuration.

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  primary:
    <<: *default
    database: my_primary_database
    user: root

  replica:
    <<: *default
    database: my_primary_database
    user: root_readonly
    replica: true

The model to use the database connections will be configured as below:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :replica }
end

Rails has also introduced a middleware to automatically switch connections based on the HTTP verb. The following lines will have to be added to the application config.

config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session

The requests are routed as follows:

  • The request is sent to the replica if the request is a read request (GET, HEAD) AND the last write to the database was made 2 seconds ago or more.
  • The request is sent to the primary if the request is a non-read request ( POST, DELETE, … ) OR the last write to the database was made less than 2 seconds ago.

The delay mentioned as 2 seconds is the default. It is required to allow the replica to catch up to the primary and prevent stale reads because of the replication lag.

It can be configured as below:

config.active_record.database_selector = { delay: 5.seconds }

Tuesday, 18. February 2020

Lucas Caton

Segunda temporada do Podcast!

Fala meus caros! 👋

No começo desse ano (também conhecido como “mês passado”), eu estreei a 2ª temporada do meu podcast, no qual estou entrevistando profissionais que trabalham com tecnologia (a maioria programadores) que eu admiro.

Toda quinta-feira tem um episódio novo, com insights de quem está no “campo de batalha”, trabalhando na área.

Os 5 primeiros episódios já estão no ar e você pode escutar através do seu player de podcast preferido; é só buscar por Lucas Caton no app de podcast ou conferir o link direto nessa página.

Se preferir, você também pode assistir pelo YouTube:

Episódio 01 - WILL ROSA

CURIOSO, AUTODIDATA, COMPARTILHADOR DE CONTEÚDO E LEITOR DE MANUAIS

Episódio 02 - THIAGO ALESSIO

PROGRAMADOR (INCLUSIVE DE ATARI), SYSADMIN E QUASE ALEMÃO

Episódio 03 - VAL SILVA

ANALISTA DE SISTEMAS, GUITARRISTA, DOUTORANDO EM COMPUTAÇÃO E HUMILDÃO

Episódio 04 - FERNANDO SOUSA

EMPREENDEDOR, RANGER E DESENVOLVEDOR DESDE OS 10 ANOS

Episódio 05 - MAURICIO DE AMORIM

ESTAGIÁRIO AOS 36, CORREDOR, EMPATIA MAN, O CARA DOS BASTIDORES

E é só o começo, toda semana tem episódio novo! 😉


JRuby.org News

JRuby 9.2.10.0 Released

The JRuby community is pleased to announce the release of JRuby 9.2.10.0

JRuby 9.2.x is compatible with Ruby 2.5.x and stays in sync with C Ruby. As always there is a mix of miscellaneous fixes so be sure to read the issue list below. All users are encouraged to upgrade.

Ruby Core

  • Kernel#send and BasicObject#__send__ will now honor refinements used in the surrounding scopes. (#5945, #6045)
  • Kernel#eval will now honor refinements used in a given binding. (#6017)
  • Process#spawn now correctly processes environment variables for the child process. (#3428, #5907)

Ruby Standard Library

  • RubyGems has been updated to version 3.0.6. (#5995, #5996)
  • The jruby-openssl library has been updated to 0.10.4. This release improves support for newer forms of ciphers and cryptographic keys and reduces the number of Java module warnings displayed. (#6077)
  • The compatibility of the fiddle library has been improved. (#5955, #5956, #5960)

Java Support

  • Java methods looked up one-at-a-time using java_method and friends now honor Java modules. This fixes some illegal access errors introduced in JRuby 9.2.9.0. (#5894, #5969, #6043, #6069, #6072)
  • More illegal access warnings have been cleaned up in JRuby and the jruby-openssl extension.

Native FFI Improvements

  • The native subsystem has been rebuilt on MacOS to avoid security warnings due to the old toolchain originally used to build it. (#6044, #6074)
  • Native constants have been updated for Windows to better support the Ruby socket API. (#5656, #6070, #6071)

Performance Improvements

JRuby Launcher

  • Java options dotfiles accidentally omitted in the JRuby 9.2.9.0 release have been restored in the bin directory.
  • The bash-based launcher script has been cleaned up and optimized for cross-platform compatibility. (#5977, #6007, #6016)

Thank You!

The JRuby project would not be possible without contributions from you!

This release includes pull requests or major contributions from the following Github users (in no particular order): @matthewd, @fidothe, @MariuszCwikla, @ahorek, @deivid-rodriguez, @pythias, @rotty3000, @rmannibucau, @Mrnoname1000, @kares, @mkristian, @eregon

Github Issues resolved for 9.2.10.0

  • #6077 - update jruby-openssl to 0.10.4
  • #6074 - Update jnr dependencies
  • #6073 - Fix windows_stub_script to use jruby.exe
  • #6072 - Type Error: illegal access for final methods on ReferencePipeline (stream) in Java 11
  • #6071 - socket addrinfo fixes
  • #6070 - ipproto values are missing on Windows
  • #6069 - Generify and reuse accessible method search
  • #6067 - optimize roundUpToPowerOfTwo
  • #6057 - Bump mavengem-wagon version everywhere
  • #6054 - Refined send
  • #6044 - libjffi should be rebuilt on darwin (using newer xcode)
  • #6043 - Java 11 Compatibility
  • #6040 - make jruby-core-source jar has no absolute path from home dir
  • #6039 - NullPointerException from FStringEqual.hashCode
  • #6038 - profiler crashes with `java.lang.RuntimeException: BUG: refined marker called as method`
  • #6036 - Enumerator#take duplicates objects
  • #6035 - Prepare io/console for move to gem.
  • #6032 - [fix] restore $! on non-local return from rescue
  • #6031 - [fix] restore rescue nil performance
  • #6030 - Deprecation warning message : Gem::ConfigMap
  • #6029 - [fix] inspect builtin
  • #6028 - revert rational test
  • #6027 - [fix] SyntaxError rational overflow
  • #6025 - SyntaxError (Rational (174532925199432957/1E+19) out of range)
  • #6024 - Add filesystem and internal encoding names
  • #6021 - Fix Enumerator fiber leaks
  • #6017 - Refinements in eval with binding
  • #6016 - Remove array expansion of JAVA_OPTS environment variable
  • #6015 - early block return retains $! filling cause for next error
  • #6014 - correct/revisit exception backtrace (and stack-trace)
  • #6007 - Bash cleanup
  • #6005 - Reduce block overhead
  • #6002 - (refinements?) Something is passing nullblock instead of the actual block in GraphQL
  • #6000 - Do not alias a Java IsEqual to equal?
  • #5999 - ensure javase version in osgi meta is 8 and not 9
  • #5998 - update maven-bundle-plugin and bndlib to latest
  • #5997 - Stack overflow due to RubyGems warn patch
  • #5996 - Update RubyGems to 3.0.6.
  • #5995 - Update RubyGems
  • #5992 - Make many fields on Ruby final.
  • #5991 - Invalid OSGi metadata
  • #5990 - The .equal? method should not be mapped to .isEqual for Java objects (e.g. Joda LocalDate)
  • #5988 - java.lang.module.ResolutionException
  • #5985 - fix strict_base64 for shared strings
  • #5984 - Bind only public interface methods for private classes
  • #5981 - Nailgun Error - org.jruby.util.NailMain: command not found
  • #5979 - Maven source artifact contains full build path
  • #5978 - Cache method_missing target
  • #5977 - fix 'jruby: line 88: cd: ../libexec/bin: No such file …'
  • #5976 - Incorrect warning line
  • #5972 - optimize isGregorianLeap
  • #5970 - expand #succ for bignums
  • #5969 - Mutliple issues on jdk11 after ugrading to jRuby 9.2.9.0
  • #5968 - Enumerator#any? is wrong for sequence of empty arrays
  • #5966 - Remove usages of deprecated ConfigMap
  • #5965 - isGregorianLeap in RubyDate.java - two suggestions for minor changes
  • #5963 - improve BUILDING.md
  • #5961 - Faster unpackm0
  • #5960 - fiddle fixes
  • #5959 - Tweak getCurrentContext for SPEED
  • #5958 - Propagate ThreadContext through to_a implementations
  • #5956 - dlload throws LoadError instead of Fiddle::DLError
  • #5955 - NameError: uninitialized constant Fiddle::CParser::TYPE_SIZE_T
  • #5953 - Fix Array.pack with @ directive and buffer #4727
  • #5952 - Use string index in String #sub/#gsub when String pattern passed without creating a Regexp
  • #5945 - Refinements are not activated in send
  • #5907 - Properly override base env with supplied values for spawn
  • #5905 - String#gsub given a String can avoid Regexp
  • #5894 - Can't call methods of private subclasses in a module
  • #5656 - Multicast Receive Broken On Windows 10
  • #4727 - Array#pack buffer should start from beginning of buffer?
  • #3428 - Process.spawn doesn't remove nil env variables
  • #2255 - Fixnum instance variable behaviour incorrect
  • #2096 - filesystem and internal encodings are missing from Encoding.name_list

RubyGems Blog

3.0.7 Released

RubyGems 3.0.7 includes bug fixes.

To update to the latest RubyGems you can run:

gem update --system

If you need to upgrade or downgrade please follow the how to upgrade/downgrade RubyGems instructions. To install RubyGems by hand see the Download RubyGems page.

Bug fixes:

  • Fix underscore version selection for bundler #2908 by David Rodríguez.
  • Add missing wrapper. Pull request #2690 by David Rodríguez.
  • Make Gem::Specification#ruby_code handle OpenSSL::PKey::RSA objects. Pull request #2782 by Luis Sagastume.
  • Installer.rb - fix #windows_stub_script. Pull request #2876 by MSP-Greg.
  • Use IAM role to extract security-credentials for EC2 instance. Pull request #2894 by Alexander Pakulov.

SHA256 Checksums:

  • rubygems-3.0.7.tgz
    e686c4aec1abcfbedc1a2b841db0ccfb22c5745af7b0fb909d22560471a9c433
  • rubygems-3.0.7.zip
    ae09e078cbde01f15dc477a959386c0dda2fa6bf433b8e68600166530c261e8d
  • rubygems-update-3.0.7.gem
    f612029bc1a9c09cd7af095f01df6cb6471d0eefd8ffae70c1799275cf2b9ffc

The Official BigBinary Blog | BigBinary

Ruby 2.7 adds Enumerable#tally

This blog is part of our Ruby 2.7 series.

Let’s say that we have to find the frequency of each element of an array.

Before Ruby 2.7, we could have achived it using group_by or inject.

irb> scores = [100, 35, 70, 100, 70, 30, 35, 100, 45, 30]

# we can use group_by to group the scores

irb> scores.group_by { |v| v }.map { |k, v| [k, v.size] }.to_h
=> {100=>3, 35=>2, 70=>2, 30=>2, 45=>1}

# or we can use inject to group the scores

irb> scores.inject(Hash.new(0)) {|hash, score| hash[score] += 1; hash }
=> {100=>3, 35=>2, 70=>2, 30=>2, 45=>1}

Ruby 2.7

Ruby 2.7 adds Enumerable#tally which can be used to find the frequency. Tally makes the code more readable and intutive. It returns a hash where keys are the unique elements and values are its corresponding frequency.

irb> scores = [100, 35, 70, 100, 70, 30, 35, 100, 45, 30]
irb> scores.tally
=> {100=>3, 35=>2, 70=>2, 30=>2, 45=>1}

Check out the github commit for more details on this.


Chris Mytton @ Bristol › England

Ruby’s Time vs DateTime classes

Ruby has two classes, Time 1 and DateTime, that look quite similar at a glance. They both have methods for dealing with dates and times. Which should you choose when you need to work with dates and times in Ruby?

It’s a common misconception that William Shakespeare and Miguel de Cervantes died on the same day in history - so much so that UNESCO named April 23 as World Book Day because of this fact. However, because England hadn’t yet adopted the Gregorian Calendar Reform (and wouldn’t until 1752) their deaths are actually 10 days apart. Since Ruby’s Time class implements a proleptic Gregorian calendar and has no concept of calendar reform there’s no way to express this with Time objects. This is where DateTime steps in:

shakespeare = DateTime.iso8601('1616-04-23', Date::ENGLAND)
 #=> Tue, 23 Apr 1616 00:00:00 +0000
cervantes = DateTime.iso8601('1616-04-23', Date::ITALY)
 #=> Sat, 23 Apr 1616 00:00:00 +0000

Already you can see something is weird - the days of the week are different. Taking this further:

cervantes == shakespeare
 #=> false
(shakespeare - cervantes).to_i
 #=> 10

This shows that in fact they died 10 days apart (in reality 11 days since Cervantes died a day earlier but was buried on the 23rd). We can see the actual date of Shakespeare’s death by using the gregorian method to convert it:

shakespeare.gregorian
 #=> Tue, 03 May 1616 00:00:00 +0000 So there's an argument that all the celebrations that take place on the 23rd April in Stratford-upon-Avon are actually the wrong date since England is now using the Gregorian calendar. You can see why when we transition across the reform date boundary:

# start off with the anniversary of Shakespeare's birth in 1751
shakespeare = DateTime.iso8601('1751-04-23', Date::ENGLAND)
 #=> Tue, 23 Apr 1751 00:00:00 +0000

# add 366 days since 1752 is a leap year and April 23 is after February 29
shakespeare + 366
 #=> Thu, 23 Apr 1752 00:00:00 +0000

# add another 365 days to take us to the anniversary in 1753
shakespeare + 366 + 365
 #=> Fri, 04 May 1753 00:00:00 +0000

As you can see, if we’re accurately tracking the number of solar years since Shakespeare’s birthday then the correct anniversary date would be the 4th May and not the 23rd April.

So when should you use DateTime in Ruby and when should you use Time? Almost certainly you’ll want to use Time since your app is probably dealing with current dates and times. However, if you need to deal with dates and times in a historical context you’ll want to use DateTime to avoid making the same mistakes as UNESCO. If you also have to deal with timezones then best of luck - just bear in mind that you’ll probably be dealing with local solar times, since it wasn’t until the 19th century that the introduction of the railways necessitated the need for Standard Time and eventually timezones.

From “When should you use DateTime and when should you use Time?” in the Ruby stdlib DateTime documentation.

Summary

  • Use Time when dealing with near-past, present or future dates. It can technically represent dates from 1823-11-12 to 2116-02-20.
  • Use DateTime when you want to accurately model distant past dates, like Shakespeare’s birthday.
  • So for most applications use Time.
  1. There are actually two Time classes, one in core and one in the standard library which you get when you or one of your dependencies does a require 'time'


Saeloun

Ruby 2.7 removes taint checking mechanism

Taint Checking is a mechanism that was used by Ruby to prevent malicious commands from being executed on a host machine.

As per taint checking, any input that originated or can be modified by an external user (such as a form field), is considered tainted. If this tainted object is used in an expression to evaluate another object, then that gets tainted as well.

While running in a safe mode, if the program tries to execute a potentially dangerous method using any tainted object, then it will raise a SecurityError.


The $SAFE global variable

Ruby security level is set and accessed by the $SAFE global variable. The possible values for this variable ranges from 0 to 4, 4 being the maximum security level.

More info about the $SAFE variable can be found here.


Why is taint checking being removed?

Taint checking features were used extensively in the age of CGI programs. But in recent times, most input libraries don’t support this mechanism. E.g., Rack.

In addition to that, there have been multiple vulnerabilities around the $SAFE variable and the taint mechanism altogether. Source.


Ruby 2.7

In Ruby 2.7, the taint checking mechanism has been removed. This means all objects by default are deemed untainted.

Accessing and setting the value of $SAFE variable will always result in a warning.

Also, Object#taint, Object#trust, Object#untaint, Object#untrust and related functions in the C-API have been made no-op methods. It means they do nothing and just return self. Their usage will result in a verbose warning (warned only in verbose mode).

Complete removal plan

As per the feature ticket Feature #16131, the decided removal plan for taint checking is as follows:

Ruby 3.0:
- $SAFE will become a normal global variable and there will be no warning on its access or assignment.
- Usage of Object#taint, Object#trust, Object#untaint, Object#untrust and the C functions will result in a non-verbose warning (warned in both regular and verbose mode).

Ruby 3.2:
- Object#taint, Object#trust, Object#untaint, Object#untrust and the C functions will be completely removed.


Notes By Strzibny

Running Docker in Fedora 31

Fedora is a forward-thinking distribution and with the progress happening to Podman Fedora 31 switched to CGroupsV2.

Here is the summary of the change:

The kernel has had some support for CgroupsV2 for some time, and yet no one has used it because it is not on by default. There are lots of new features and fixes over CgroupsV1 that it is time to reveal to the user community.

While Docker does not work with CGroupsV1, Podman, compatible Docker replacement from the Red Hat folks does and is available.

What are even those cgroups you ask? Here’s a snippet from Wikipedia:

cgroups (abbreviated from control groups) is a Linux kernel feature that limits, accounts for, and isolates the resource usage (CPU, memory, disk I/O, network, etc.) of a collection of processes.

So cgroups are the thing that constrains the container resources on a Linux system (and kind of give them the meaning to exist…).

To install Podman just run dnf install podman as with any other package:

$ sudo dnf install podman

Done! Now start using podman instead of docker:

$ podman pull ...
$ podman ps

There are two more reasons to give Podman a go:

  • It’s daemon-less so you don’t need a running service to administer your Docker containers.
  • It does not require root privileges.

If for some reason Podman does not work for you, you need to downgrade to CGroupsV1 to continue with Docker CE as usual.

Edit /etc/default/grub as root and append to GRUB_CMDLINE_LINUX:

systemd.unified_cgroup_hierarchy=0

This is how /etc/default/grub file looks like on my system after the change:

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/root rd.luks.uuid=luks-53d3010b-fd1b-4c42-9b83-a37cb8d6db8a rd.lvm.lv=fedora/swap rhgb quiet systemd.unified_cgroup_hierarchy=0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

Then use this new default config as configuration for your boot loader:

$ sudo grub2-mkconfig > /boot/efi/EFI/fedora/grub.cfg

Don’t forget to reboot:

$ reboot

Once rebooted, start the Docker daemon as usual:

$ sudo systemctl start docker

Monday, 17. February 2020

RubyGuides

How to Work With Directories in Ruby

Did you know that you can navigate your file system with Ruby?

With the Ruby “Dir” class.

You can list directory entries, change your current directory & even create new folders!

Here’s an example:

filenames = Dir.entries(".")

This entries method returns an array of filename entries. Every entry is a string, so you’ll need to combine this with the Ruby File class if you want to actually read these files.

Btw, this dot (.) represents the current directory.

This will be the directory your code is running from, NOT the directory you’re (in your terminal) when running the code.

Because of that…

It can be helpful to check your current directory by using the Dir.pwd method.

Now:

Let’s learn how to use the Dir class to create new directories (mkdir), rename them (mv), and find file names that follow a specific pattern (glob).

Using Ruby’s Mkdir Method To Create A New Directory

If you want to create a new folder with Ruby you can use the Dir.mkdir method.

Example:

Dir.mkdir("testing")

If given a relative path, this directory is created under the current path (Dir.pwd).

You can get a few errors:

  • Directory already exists (Errno::EEXIST)
  • Permission denied (Errno::EACCES)
  • You’re trying to create a folder under another folder that doesn’t exist yet (Errno::ENOENT)

The last error usually happens when you’re trying to create nested directories.

Or if you’re using an absolute path that doesn’t exist.

Two solutions:

  • Check if the directory exists before creating it (with Dir.exists?)
  • Use a more advanced class (next section)

Let’s keep learning!

Advanced Operations With The FileUtils Module

If you need an extra bit of horsepower you can bring in the FileUtils module from the standard library.

It includes methods like mkdir_p that create nested directories in one step.

Here’s an example:

require 'fileutils'

FileUtils.mkdir_p("/tmp/testing/a/b")

Pretty cool, right?

That’s not all, FileUtils also brings extra options for all file operations in the form of keyword arguments. Like the verbose option (prints Linux commands) & the noop (don’t change files) option.

Give it a try!

How to Rename Directories

Ruby allows you to do every operation you can do from your Operating System user interface, or from a terminal.

For example…

You can rename a directory like this:

FileUtils.mv("/tmp/a", "/tmp/b")

You’ll also need to use FileUtils here because mv is not available on the Dir class.

How to Change Your Current Directory

Because all directory operations are run from the current directory, you may want to change it.

You can use the Dir.chdir method to do this.

Example:

Dir.chdir("/tmp") { Dir.entries(".") }

This works in two ways:

  • With a block, the current directory changes only for the code inside the block
  • Without a block, it changes for all the code after the method call

Notice that chdir only works within your Ruby process, it doesn’t affect the “outside world”.

In other words…

It won’t change your shell’s working directory after your Ruby program stops running.

Listing Files & Directories With Pattern Matching

Want to find all Ruby files in a folder? An easy task with the glob method!

Example:

Dir.glob("*.rb")

You can use any other extension you want, like “.txt”, or “.yml”. Or any other text that’s part of the file name.

Want to find files inside all folders?

Yep, it’s possible:

Dir.glob("**/*.rb")

The result is an array with all the file names, including the relative path. You can remove the path & get only the file name by using the File.basename method on every file of the list that you get from glob.

Summary

You have learned how to work with directories in Ruby using the Dir & FileUtils classes.

Now it’s your turn to put this into practice.

Thanks for reading! 🙂

The post How to Work With Directories in Ruby appeared first on RubyGuides. Don't miss your free gift here :)


Lucas Caton

Instalação do Ruby & do NodeJS no Ubuntu Linux, usando ASDF


Abaixo você encontra os comandos executados no vídeo:

sudo apt update
sudo apt install git automake autoconf libreadline-dev libncurses-dev libssl-dev libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev unzip curl zlib1g-dev sqlite3 libsqlite3-dev

Site do ASDF: https://asdf-vm.com/

git clone https://github.com/asdf-vm/asdf.git ~/.asdf
cd ~/.asdf
git checkout "$(git describe --abbrev=0 --tags)"

echo -e '\n. $HOME/.asdf/asdf.sh' >> ~/.bashrc
echo -e '\n. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc
source ~/.bashrc
asdf --version

Site do Ruby: https://www.ruby-lang.org/pt/

asdf plugin-add ruby
asdf install ruby 2.6.5
asdf global ruby 2.6.5
ruby -v

Site do NodeJS: https://nodejs.org/pt-br/

asdf plugin-add nodejs
bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring
asdf install nodejs 12.14.1
asdf global nodejs 12.14.1
node -v

Site do Rails: https://rubyonrails.org/

gem install rails
rails -v

Site do Yarn: https://yarnpkg.com/

npm install --global yarn
yarn -v

Criando um projeto Rails para garantir que tudo está funcionando:
rails new todo
cd todo
rails s

Abra o navegador e acesse a página localhost:3000.


RubyMine Blog

RubyMine 2020.1 EAP4: Improvements in Navigation Between Rails Entities

RubyMine 2020.1 EAP4 is now available. In it, we’ve brushed up the navigation between Rails entities. Here’s a quick overview of what’s been implemented.

The Ability to Jump to a DB Schema

You can now navigate between Rails entities and a DB schema.

Open an entity (e.g. a Model), press ⌃⌘↑ (or Ctrl+Alt+Home on Windows) and select schema.rb. It will take you to the create_table call.

The same sequence of steps allows you to navigate back from the schema to the Model or other related entities.

Navigate from a Model to a DB schema

This feature doesn’t work with the SQL structure dump yet. Please feel free to vote for it in YouTrack to let us know if you want this feature.

To make navigation more seamless, we’ve unified the look of the Related Symbols popup. It now contains all the related Rails entities, no matter which you were in when calling it.

Navigate from a Controller to its related entities

Views and Layouts are now more compact. Clicking them will open their respective directories in the project view.

Compact Views and Layouts

A specific view will only open when a caret is placed on a Controller’s action method. You can navigate back to this method from the view as well.

Navigate from a Controller to a specific view

You can now also navigate between Mailers and their respective Views and Layouts in a similar way.

Navigate between Mailers, Views, and Layouts

For an overview of the most useful RubyMine shortcuts, check out our Navigate in RubyMine Like a Pro blog post.

Early Access Program Key Facts

  • The EAP version of RubyMine is free to use. It will expire in 30 days.
  • This is pre-release software, and it may not work as intended.
  • You can install the EAP version alongside a stable version of RubyMine.
  • EAP versions of RubyMine report statistics by default. These statistics help us improve user experience. You can opt-out by changing the settings in Preferences/Settings | Appearance & Behavior | System Settings | Data Sharing.
  • EAP versions have their own documentation as well.

Join the Early Access Program

You are welcome to download the latest EAP build from our website or via the Toolbox app.

The full list of closed tickets in this EAP build is in the release notes. Please continue to report any issues you encounter.

To learn about new features as they come out, please follow RubyMine on Twitter. We post product news and tips several times a week.

Happy Developing!
Your RubyMine team.


Rails Girls Summer of Code Blog

Alumni Interview with Ipshita Chatterjee

In the second of our series of Alumni Interviews, Ipshita Chatterjee from New Delhi tells us how the experience of working on coala with RGSoC 2017 helped jump-start her career as a software engineer. Now working as a member of Technical Staff for Adobe, Ipshita is still passionate about diversity in technology and has some brilliant advice for new students.

When did you first become interested in programming?

My first brush with Computer Science was at age 13 when I took an HTML elective in high school. The power of a few simple lines of code to create beautiful web pages fascinated me immensely and motivated me to pursue a career in programming.

I pursued a Bachelor of Engineering degree in Computer Engineering at the Netaji Subhas University of Technology at the University of Delhi, which subsequently led to my first job in the tech industry.

Did you always plan to have a career in tech?

Computer Science, to me, is empowering - a tool with which we can forge and transform our future. The sheer transformational power of technology, in impacting lives all over the world captivated me from a very young age. I have planned to pursue a career in this field ever since.

So, what was your RGSoC project all about?

I worked on writing linter bears for coala, a platform-agnostic static analyzer - a unified command-line interface for linting and fixing all your code, regardless of the programming languages you use. At the end of the summer, I was able to introduce the support for HAML linting to coala, adding to the vast repertoire of languages supported by the tool.

Which skills did you find most useful during RGSoC?

RGSoC is a program that calls for technical and non-technical skills in equal measure, as it is effectively a preview of what a software development job looks like. Coding, testing, documenting, reviewing and technical design skills are essential for working on an open source project for three months.

In addition, project management skills like breaking down large goals into smaller, incremental tasks, tracking daily progress, effective and open communication, and collaboration with your team and the larger open source community are key to a successful summer.

What challenges did you encounter during the program?

The entire experience of RGSoC, right from the application phase, has been a steep and enriching learning curve. RGSoC was my first foray into open source development and through the application process, I learnt to venture out of my comfort zone and tackle new challenges.

RGSoC gave me the opportunity to share my experience on an international platform at Codemotion Berlin, which was an unforgettable experience for me.

Initially, I was skeptical and hesitant to ask for help, but through the summer, I realized that no question is too trivial and the community is here to help out. The RGSoC team’s support allowed me to chart my own course of success, focusing on developing my skills regardless of any external benchmarks.

RGSoC made me realize the power of communication and community. I learnt to establish open and honest channels of communication, and respect and incorporate diverse perspectives. The learnings from this program have proved extremely useful for my professional and personal growth.

What do you do in your current job role?

I currently work in a software engineering role at Adobe, on a vast array of cloud technologies. A typical day comprises of coding, testing, debugging, collaborations with my teammates and learning more about the tools and technologies in use in the cloud computing world.

How did your participation in RGSoC help you get to where you are today?

RGSoC has been a life-altering experience for me and I am indebted to it for the connections and opportunities it has given me. Being a part of this wonderful community, working towards promoting diversity in technology, a cause that is very close to my heart, has been immensely fulfilling. Through RGSoC, I have been able to forge meaningful professional connections all over the world and use these learnings to kickstart my career in technology.

Do you have any advice for new RGSoC students and for women and non-binary people who wish to work in tech?

Do not be afraid to venture out of your comfort zone and try new things! It is often intimidating to see other people’s accomplishments, but everything, big and small, starts with the first step. No question is too trivial and don’t hesitate to ask for help. You’ll be amazed at how much you can achieve.

Supporting coders of the future

If you’ve been inspired by Ipshita’s story and would like to help more beginner coders get started in open source, why not donate to the RGSoC crowdfunding campaign? 100% goes towards supporting future RGSoC student teams and bringing more diversity to tech.


Saeloun

Rails 6.1 adds at option to perform_enqueued_jobs test helper

Rails provides perform_enqueued_jobs test helper to perform all enqueued jobs in the given block while testing.

  perform_enqueued_jobs do
    TestJob.perform_later("Hello") # will be performed
  end

  assert_performed_jobs 1

Let’s consider we have a Shift model which has start_time and end_time as timestamps and status to maintain shift’s status. Shift status can be started or finished.

class Shift

  enum status: [:started, :finished]
  after_create :auto_finish_shift

  def auto_finish_shift
    ShiftAutoFinishJob.set(wait_until: end_time).perform_later(id)
  end

end

We have after_save callback to schedule ShiftAutoFinishJob which will wait until shift’s end_time. ShiftAutoFinishJob is used here to automatically finish the shift.

class ShiftAutoFinishJob < ApplicationJob
  queue_as :default

  def perform(shift_id)
    shift = Shift.find(shift_id)
    shift.update status: 'finished'
  end
end
require 'test_helper'

class ShiftTest < ActiveSupport::TestCase
  test "success" do
    perform_enqueued_jobs do
      create :shift, start_time: Time.now, end_time: Time.now + 100, status: 'started'
    end

    assert_performed_jobs 0
  end
end
> rails test TEST=test/models/shift_test
# Running:

F

Failure:
ShiftTest#test_success [/test/models/shift_test.rb:9]:
0 jobs expected, but 1 were performed.
Expected: 0
  Actual: 1

We can see that ShiftAutoFinishJob has been performed. It should not be executed because shift’s end_time is later than the Time.now.

To avoid this, Rails 6.1 has added at option to perform_enqueued_jobs test helper to run only jobs which are scheduled at or before the passed in time.

With Rails 6.1

require 'test_helper'

class ShiftTest < ActiveSupport::TestCase
  test "success" do
    perform_enqueued_jobs(at: Time.now) do
      create :shift, start_time: Time.now, end_time: Time.now + 100, status: 'started'
    end

    assert_performed_jobs 0
  end
end
> rails test TEST=test/models/shift_test
# Running:

.

Finished in 0.579221s, 1.7265 runs/s, 1.7265 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

In the example above, ShiftAutoFinishJob has not been executed because the shift’s end_time is later than the passed in time, i.e. Time.now.


Notes By Strzibny

How I reached 200 subscribers for my upcoming book

This post is about a journey from 0 to 200 e-mail subscribers for my upcoming book. As I always enjoyed similar posts by fellow authors and indie hackers, I decided I will document my journey to writing, publishing, and promoting my first book.

I got my first real subscriber on 27 September 2018. It took me exactly 159 days until I reached 100 subscribers on 4 March 2019. It’s said that the first one hundred is the most difficult, but that’s not entirely true here.

As part of my validation I created a simple landing page, posted about the idea on the Ruby subreddit and to this blog. I waited and listened for feedback of the idea.

As you can see below it was a successful post. It actually took me by surprise as it got more upvotes than many of my useful Ruby articles! 92% upvoted. I really really like the Ruby community. That gave me both the confidence to continue and my first subscribers.

reddit

The post itself game me over 50 people in 2 days.

On 11 February 2018 I sent my first book update to the 92 subscribers from the book landing page. This was not only a first book update, but a first e-mail campaign I ever sent. 4 people unsubscribed after this. At this time the book was marketed as “VPS for Makers”.

It took me another 334 days to reach 100 more subscribers. Most of my other other subscribers came from my blog although this was not directly measured. My blog enjoyed on average 350+ unique visitors all that time and I put a small corner image link to drive traffic to the book homepage. The book was renamed to “Deployment for Makers”.

You might be interested how I get traffic for this blog since it was the main driver of the traffic. By far the most of the traffic comes from Google searches, but the big spikes of the new posts are generated by Ruby and Elixir subreddits, RubyFlow, Ruby and Elixir newsletters (thank you guys for thinking my articles are valuable to share!).

The book was at last renamed to “Deployment from Scratch”. Also the original landing page (few times tweaked in terms of text) was just redesigned. This should be the last name for the book, but one never knows…

It took 493 days in total to achieve this. That’s roughly 1.35 years. Not an overnight success at all. I sent 3 updates in that time and there were 6 unsubscribers in total.

The next update will go to more than 200 email boxes!

In short:

  • 493 days to reach 200 subscribers
  • Validation via Reddit announcement
  • Most people likely came from this blog
  • 2 book renames (and 2 domain changes)
  • 3 updates, 6 unsubscribed

If you want to continue the backstage journey of my book, follow Deployment from Scratch on Indie Hackers or follow me on Twitter. Drop me a DM if you want me to blog about something specific.

Sunday, 16. February 2020

Notes By Strzibny

How I use Sublime Text 3 for publishing my Jekyll-based blog

Recently I migrated this very blog from WordPress to Jekyll. One question that inherently follows is how do I write my posts now? Of course, any Markdown editor would do, but I was quite happy to find out there is a Jekyll plugin for my favorite editor Sublime Text that easy the publication.

Installation and configuration

To install jekyll-sublime plugin hit CTRL+SHIFT+P to open Package Control and start typing “Install Package” followed by ENTER, then type “Jekyll” and confirm again.

This is no different than any other Sublime Text plugin installation. As for the configuration, the plugin needs some basic paths to be set. I recommend creating a specific ST project for your website or blog and then use project settings as follows:

{
  "folders":
  [
    {
      "path": "."
    }
  ],
  "settings":
  {
    "Jekyll":
    {
      "jekyll_posts_path": "/home/strzibny/Projects/blog/_posts",
      "jekyll_drafts_path": "/home/strzibny/Projects/blog/_drafts",
      "jekyll_uploads_path": "/home/strzibny/Projects/blog/uploads",
      "jekyll_templates_path": "/home/strzibny/Projects/blog/_templates",
      "jekyll_markdown_extension": "md"
    }
  }
}

Do not forget to change the above to your actual absolute paths. As you can see I also prefer .md as an extension for my files (.markdown would be default). If needed, go through all the options.

Then also add the following two value pairs to Sublime user settings for Markdown completions to work:

  ...
  "auto_complete_selector": "text.html.markdown",
  "auto_complete_triggers": [ {"selector": "text.html.markdown"} ]
  ...

Workflow

So what’s the actual workflow? Jekyll plugin adds some new commands to Package Control. First we should create /_templates directory, hit CTRL+SHIFT+P to open Package Control start typing “New template”.

Once we have a page/post-template ready, we can use “New post from template” command to start editing a new post. Alternatively, we would start with a draft by invoking “New draft from template”. After completing the draft we could promote it to a post with “Promote draft to post”.

During writing we could “Insert upload” to a post as long as we have it ready inside the /uploads directory.

We have also snippets and completions to our disposal.

Yes or no

I like the minimalist approach that Jekyll plugin for Sublime Text is taking. A few things that you remember quickly and which speeds up your publication lifecycle without too much fuss.

Yes, there are no real GUI or preview features, but that’s expected. Moreover, it can play nicely with other plugins like MarkdownPreview. And besides, you might be running jekyll serve in the background anyway. I still hope to find something more productive for handling images to complement it.

Saturday, 15. February 2020

Antonio Cangiano (Zen and the Art of Programming) @ Penticton, BC › Canada

IBM Summer 2020 Internship Positions

My team at IBM is looking to hire new interns. We have several new paid internship positions in Markham, Ontario (Canada), starting in the summer of 2020.

IBM Summer 2020 Internship Positions

This is an amazing opportunity if you meet the following requirements:

  • You are legally allowed to work in Canada.
  • You are enrolled in university or college.
  • A start date in May for 16 months (or in September for 12 months) works for you.

I’m certainly biased, but I believe these are some of the best tech internships you can get in Canada.

We treat interns as peers and invest in their development. Previous interns rave about their experience and how it enabled them to grow professionally.

The best interns often end up coming back to us as full-time employees upon graduation. So this is a great way to get your foot into the industry.

Apply for the internship positions

Please use the following links to apply for the position. If you think you qualify for more than one role, feel free to apply for more than one:

If you want to speed up the process, after applying, send me an email with your resume attached.

How the interviewing process works

I will review all the applicants and select the most promising ones, scheduling a first video interview via web. This interview is with me and, usually, one of my developers (or a data scientist in the team, depending on the position).

We will ask you about your past experience as well as some technical questions. There will be no brain teasers or anything like that. The interview will last between 20-50 minutes.

The questions I tend to ask are foundational in nature. I might ask you about the difference between GET and POST requests, or 400s and 500s HTTP status codes, but not what 508 is used for. You don’t need to brush up on your Algorithms and Data Structures book either.

If you don’t know the answer, we change the subject. I’m hoping to surface your strengths during the interview, not drill you on your weaknesses.

As far as technical interviews go, this will likely be one of the least challenging and stressful you’ll encounter.

After this first interview, the most promising candidates will be invited to a second web interview with myself and my manager (an IBM Director). This second interview is much more focused on your interests and ambitions.

It will also be an opportunity for you to ask questions and learn more about our team and the work we do.

What we value

We love to have a diverse pool of interns.

If you are an amazing coder with weaker communication skills, that’s alright. Less technically strong but are an amazing communicator? That’s great. If you are keen on teaching others difficult technical concepts, we have room for you, too.

We value all the skills you can bring to the team, not just your ability to code. Above all, we value attitude and passion over raw experience.

If you are looking for a great internship opportunity, give this one a chance. I look forward to working with you.

The post IBM Summer 2020 Internship Positions appeared first on Programming Zen.

Friday, 14. February 2020

OneBitCode

Porque NÃO DESISTIR da Programação hoje

Já se sentiu desmotivado e com vontade de largar a programação?
Eu listei 7 motivos pelos quais você NÃO DEVE DESISTIR da programação hoje! 👊
-> Treinamento Programador Full Stack Javascript: https://programador.onebitcode.com

O post Porque NÃO DESISTIR da Programação hoje apareceu primeiro em OneBitCode.


Saeloun

Rails adds ActiveRecord API for switching multiple database connections

Rails has introduced support for multiple databases in application.

Rails now provides a method connects_to which we need to specify at the model level and mention the databases as shown below:

class User < ApplicationRecord
  connects_to database: { writing: :primary, reading: :replica }
end

As specified in the connects_to block above, all the update and insert queries will be executed on the primary database and select queries on replica.

connects_to method must be specified in the common base class, let’s say ApplicationRecord and this can be inherited by other models as needed. Database clients have a limit on the number of open connections and hence we need to specify connects_to in a single model base class. If this is not done, the number of open connections will add up since Rails uses the model class name for the database connection.

The database.yml for the above configuration may look like as below:

development:
  primary:
    database: my_primary_database
    user: root
    adapter: mysql

  replica:
    database: my_primary_database
    user: root_readonly
    adapter: mysql
    replica: true

We need to specify replica: true in our replica configuration because Rails need a way to identify which one is a replica and which one is primary. The primary and replica database name will be the same, but the user permissions should be different.

Outside the model, we might want to connect to a completely different database.

Let’s say for the above example, if we have another archive database for storing deactivated user, we can use Rails connected_to method as shown below:

ActiveRecord::Base.connected_to(database: :archive_db) do
  User.fetch_all_deactivated
end

:archive_db is the database key in our database.yml config file.

We can connect to a different database by using the role parameter too.

ActiveRecord::Base.connected_to(role: :reading) do
  # finds user from replica connected to ApplicationRecord
  User.first
end

By default the :writing role will be used since all connections must be assigned a role. If you would like to use a different role you can pass a hash to the database:

ActiveRecord::Base.connected_to(database: { readonly_slow: :replica }) do
  # runs a long query while connected to the +replica+ using the readonly_slow role.
  User.run_a_huge_query
end

Rails by default expect database roles to be writing and reading for the primary and replica respectively. If we want to have our roles, we need to create them and specify them in our application config.

config.active_record.writing_role = :writes
config.active_record.reading_role = :readonly

NOTE:

  • When using the database key, a new connection will be established every time. It is not recommended to use this outside of one-time scripts.

Thursday, 13. February 2020

Code With Jason

Understanding Rails secrets/credentials

What this feature is for

The credentials feature is a way of storing secrets that you don’t want to keep in plaintext, like AWS credentials for example. (In fact, the one and only thing I keep in my main Rails project’s credentials are my Active Storage AWS credentials.)

Why the credentials feature is difficult to learn about

I personally have found Rails credentials really hard to understand. I think there are three reasons why this is.

  1. The official Rails docs about the credentials feature are a little terse and not easily discoverable.
  2. The feature changed somewhat drastically from Rails 4 to Rails 5, even changing names from “secrets” to “credentials”.
  3. I only need to use the feature once in a while, when I’m deploying an application for the first time, meaning there’s lots of time in between to forget everything I knew.

How to work with credentials without frustration

There are perhaps five important things to understand about credentials:

  • Where they’re stored
  • How the master key works
  • How editing credentials work
  • What the deal is with secrets.yml
  • The steps you need to take to set up credentials on a fresh production machine
  • Credentials are stored in config/credentials.yml.enc

    At the risk of stating the obvious, your secrets are stored in config/credentials.yml.enc. That file is encrypted.

    There of course needs to be a way to securely decrypt this encrypted file. That’s done using something called a master key. The master key is just a hash string that gets stored in one of two places: a file called config/master.key (which should NOT be committed to version control) or a RAILS_MASTER_KEY environment variable.

    The development master key and production master key are the same

    A key thing to understand, which I found counterintuitive, is that the master key you use in production should be the same as the master key you use in development. If your development master key is stored in config/master.key, create an identical config/master.key on production, containing the same exact key. If your development master key is stored in the RAILS_MASTER_KEY environment variable, set the production RAILS_MASTER_KEY to the exact same value.

    I found this counterintuitive because usually I try to make all my passwords, etc. different for each environment I have. I thought I would need to create a different master key for my production environment. No, I need to not create a different master key.

    The credentials.yml.enc file is edited in a special way

    Since it’s encrypted, the config/credentials.yml.enc file can’t be edited directly. It can only be edited using the rails credentials:edit command.

    What often throws me for a loop is that a prerequisite to using rails credentials:edit is having the EDITOR environment variable set, which on a fresh production machine I usually don’t. I’m a Vim guy, so I run export EDITOR=vim and then I’m good to go. Then I can run rails credentials:edit and the command will open the credential file, decrypted, in Vim.

    secrets.yml is obsolete

    If you find something online that refers to secrets.yml, you’re looking at an old post. Before Rails 5.2, there was a secrets.yml and secrets.yml.enc instead of the new credentials-related files. Don’t make the mistake of conflating Rails secrets with Rails credentials (like I did several times before learning better!).

    The steps for setting up credentials in production

    1. Take the same master key you’re using in development and put it either in config/master.key or the RAILS_MASTER_KEY environment variable.
    2. Set the EDITOR environment variable to your favorite terminal-based editor.
    3. Run rails credentials:edit to verify that your master key is working properly.

    Helpful links

    I hope my credentials guide is the new best guide on the internet but I’ll link to the sources that helped me put this together.

    Best of luck with your credential management endeavors.

    The post Understanding Rails secrets/credentials appeared first on Code with Jason.

Wednesday, 12. February 2020

Fast Ruby Blog

How to Calculate Tech Debt Using Skunk on GitHub Actions

In preparation for my talk at RubyConf Australia this month, I've been working on a way to make it easy for anyone to run skunk on their Ruby projects. In order to do that I decided to use GitHub Actions. It's a powerful service by GitHub and it's quite easy to set up.

This is an article about the process that I followed and how you can use it in your own application.

GitHub Actions have been around for more than a year and I had been meaning to play around with them to incorporate some automation to our workflows at FastRuby.io. The good news is that GoRails already published an article about setting up CI in your Rails app: Continuous Integration with GitHub Actions

After following those steps, I ended up with a copy/pasted YAML file that looked like this:

# .github/workflows/ci.yml

name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      db:
        image: postgres:11
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis
        ports: ['6379:6379']
        options: --entrypoint redis-server

    steps:
      - uses: actions/checkout@v1
      - name: Setup Ruby
        uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.6.x
      - uses: borales/actions-yarn@v2.0.0
        with:
          cmd: install
      - name: Build and run tests
        env:
          DATABASE_URL: postgres://postgres:@localhost:5432/test
          REDIS_URL: redis://localhost:6379/0
          RAILS_ENV: test
          RAILS_MASTER_KEY: $
        run: |
          sudo apt-get -yqq install libpq-dev
          gem install bundler
          bundle install --jobs 4 --retry 3
          bundle exec rails db:prepare
          bundle exec rails test

Considering that skunk is a Ruby gem (it doesn't need Rails, Redis, nor Postgres) and I didn't need all the steps I copied from the GoRails tutorial, I thought it was best to simplify it to look like this:

# .github/workflows/skunk.yml

name: Skunk
on: [push]

jobs:
  skunk:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: Setup Ruby
        uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.6.x
      - name: Run Skunk on Project
        run: |
          gem install skunk
          skunk lib/

This tells GitHub to run the skunk action every time there is any push to GitHub. To see an entire list of events that you can configure: Webhook Events

There are only two steps:

  1. Setup my Ruby environment. I'm going to need this because skunk is a Ruby gem.
  2. Install the skunk gem and run it on the lib/ directory.

I really like that the configuration file is easy to read and understand. The order of the steps defined in the steps section is important. It will run steps synchronously, from top to bottom. If you want to run skunk for a Rails application, you can change the last step to skunk app/

The next thing I wanted to do is run skunk on a pull request in order to compare the Stink Score between the pull request and master. This will help us answer this question:

Are we increasing or decreasing the tech debt average in our project?

In order to do this, I had to tweak the call to skunk to use the --branch option:

# .github/workflows/skunk.yml

name: Skunk
on: [push]

jobs:
  skunk:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: Setup Ruby
        uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.6.x
      - name: Run Skunk on Project
        run: |
          gem install skunk
          CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
          if [[ "$CURRENT_BRANCH" != "master" ]]; then
            echo "Executing within branch: $CURRENT_BRANCH"
            skunk lib/ -b master
          else
            echo "Executing within master branch"
            skunk lib/
          fi

There is some bash logic in there: The GitHub Action will compare your branch vs. master if it is running within the context of a pull request, or generate a tech debt report if it is running a commit pushed to the master branch.

One last thing I had to add was a step to generate SimpleCov's resultset JSON file. Skunk is most useful when it considers code coverage data. The StinkScore is a combination of code smells; complexity; and code coverage data.

I tweaked the steps configuration to look like this:

steps:
  - uses: actions/checkout@v1
  - name: Setup Ruby
    uses: actions/setup-ruby@v1
    with:
      ruby-version: 2.6.x
  - name: Run test suite with COVERAGE=true
    run: |
      gem install bundler
      bundle install --jobs 4 --retry 3
      COVERAGE=true bundle exec rake test
  - name: Run Skunk on Project
    run: |
      gem install skunk
      CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
      if [[ "$CURRENT_BRANCH" != "master" ]]; then
        echo "Executing within branch: $CURRENT_BRANCH"
        skunk lib/ -b master
      else
        echo "Executing within master branch"
        skunk lib/
      fi

Now you can see the difference in tech debt between master and your pull request:

Base branch (master) average stink score: 13.42
Feature branch ((HEAD detached at 0315f34)) average stink score: 13.42
Score: 13.42

This particular example shows that there is no difference in my application's technical debt: https://github.com/fastruby/skunk/pull/26. That's because I didn't change any Ruby code (just a YAML file).

You can see the GitHub Action run over here: https://github.com/fastruby/skunk/runs/437118684

Final Thoughts

GitHub Actions can be very useful when you want to run something really quickly without setting up your own development environment. It can take a lot of trial and error to get the final configuration right.

If you wanted to give skunk a try, now there are no more excuses. You don't need to install anything in your environment. You can add this GitHub workflow and that's it:

name: Skunk
on: [push]
jobs:
skunk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: actions/setup-ruby@v1
with:
ruby-version: 2.6.x
- name: Run test suite with COVERAGE=true
run: |
gem install bundler
bundle install --jobs 4 --retry 3
COVERAGE=true bundle exec rake test
- name: Run Skunk on Project
run: |
gem install skunk
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if [[ "$CURRENT_BRANCH" != "master" ]]; then
echo "Executing within branch: $CURRENT_BRANCH"
skunk lib/ -b master
else
echo "Executing within master branch"
skunk lib/
fi
view raw skunk.yml hosted with ❤ by GitHub

I hope you can use this free and open source tool to find out your tech debt hot spots!

References


Saeloun

Rails introduces disallowed deprecations in ActiveSupport

As we upgrade our application’s Rails version, we often come across deprecation warnings.

For example:

> user.update_attributes(name: 'New name')
DEPRECATION WARNING: update_attributes is deprecated and will be removed from
Rails 6.1 (please, use update instead)
(0.1ms)  begin transaction
User Update (2.7ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE
"users"."id" = ? [["title", "New name"], ["updated_at", "2019-05-11 11:32:48.075786"],
 ["id", 1]]
(1.1ms)  commit transaction
=> true

In the above case, we remove these deprecation warnings by replacing update_attributes to update.

But, when developing new features after it, we might use end up using deprecated update_attributes again. We might ignore these deprecation warnings and on next Rails upgrade we will encounter new issues.

To avoid the above case, Rails has introduced disallowed deprecations in ActiveSupport. Once we have removed deprecations from codebase, we configure these deprecations as disallowed. If a disallowed deprecation is used, it will be treated as a failure and raise an exception in development or test environment. In production, we log the deprecations as error.

We can configure the disallowed_warnings in our config file as below:

ActiveSupport::Deprecation.disallowed_warnings = [
  "update_attributes",
   :update_attributes,
  /(update_attributes)!?/,
]

Configuration rules as seen above are defined in an array. Array elements can be String, Symbol or Regexp. In each case we try to match the array element as a substring or regex match in our warning message.

Disallowed configuration can be set to raise exception for all deprecation warning by using :all symbol as below:

ActiveSupport::Deprecation.disallowed_warnings = :all

We can set the behavior for disallowed messages to either :raise, :log or :notify.

ActiveSupport::Deprecation.disallowed_behavior = [:log]

By default, it is set to :raise in development and test environment and :log in production environment.

We can temporarily re-allow disallowed deprecation using the .allow method.

ActiveSupport::Deprecation.allow do
  User.code_that_calls_update_attributes_method
end

We can also pass an array of string, symbol or regular expression elements to this method.

ActiveSupport::Deprecation.allow [:update_attributes, "update_attributes!"] do
  User.code_that_calls_update_attributes_method
end

The .allow method call also be called conditionally as shown below

ActiveSupport::Deprecation.allow [:update_attributes], if: Rails.env.production? do
  User.code_that_calls_update_attributes_method
end

Tuesday, 11. February 2020

Lucas Caton

Frameworks do Rails

Nesse vídeo, eu vou destrinchar a teoria por traz dos frameworks/componentes do Rails na prática, com exemplos e tudo mais! 😉

Entenda o que é e como funciona o Active Record, Action View, Action Controller, Active Support, Action Mailer, Action Mailbox, Action Text, Active Job, Active Storage, Action Cable, etc.

Com esses conceitos fica mais fácil compreender como as coisas se encaixam para explorar novas soluções para suas aplicações.

Se curtir, não deixe de se inscrever no canal! 😉


Saeloun

Rails has added a benchmark generator

Rails added a new benchmark generator. This is used to create benchmark file that uses benchmark-ips.

To use the benchmark generator, run

rails generate benchmark my_benchmark

This creates a benchmark file script/benchmarks/my_benchmark.rb. It also adds benchmark-ips gem to Gemfile. Be default, it adds 2 blocks for reporting before and after that can be used for benchmarking.

The file content looks like following:

# frozen_string_literal: true

require_relative "../../config/environment"

# Any benchmarking setup goes here...



Benchmark.ips do |x|
  x.report("before") { }
  x.report("after") { }

  x.compare!
end

This benchmark can be run using following command:

ruby script/benchmarks/my_benchmark.rb

Since we load config/environment.rb file, the Rails application environment is available to us in the script. The script runs in development environment.

We can pass different reports to add to benchmark as arguments to generate command as follows:

rails generate benchmark my_benchmark_2 report1 report2 report3

This will generate benchmark file as following:

# frozen_string_literal: true

require_relative "../config/environment"

# Any benchmarking setup goes here...



Benchmark.ips do |x|
  x.report("report1") { }
  x.report("report2") { }
  x.report("report3") { }

  x.compare!
end

The main purpose of adding the generator is it provides a convenient way to setup and perform benchmarks using the benchmark-ips gem.

Also since it generates and puts the files in script/benchmarks folder it automatically sets a standard approach to place the benchmark files in the Rails application.

Monday, 10. February 2020

Saeloun

Rails adds support for if_exists/if_not_exists on remove_column/add_column in migrations

In Rails, when we have a table and we want to add or remove columns from that table, then we write migrations using methods like add_column for adding column and remove_column for removing a column.

Typical add_column and remove_column migrations look like these:

class AddDescriptionToProduct < ActiveRecord::Migration[5.1]
  def change
    add_column :products, :description, :text
  end
end
class RemoveDescriptionFromProduct < ActiveRecord::Migration[5.1]
  def change
    remove_column :products, :description
  end
end

If the column is already present and we try to run add_column migration then we get an error like this:

DuplicateColumn: ERROR:  column "description" of relation "products" already exists

Similarly, if we try to remove a non-existent column using remove_column migration, we get an error like this:

UndefinedColumn: ERROR:  column "description" of relation "products" does not exist

Before Rails 6.1

To handle these errors, we can rewrite above migrations like these:

class AddDescriptionToProduct < ActiveRecord::Migration[5.1]
  def change
    unless ActiveRecord::Base.connection.column_exists?(:products, :description)
      add_column :products, :description, :text
    end
  end
end
class RemoveDescriptionFromProduct < ActiveRecord::Migration[5.1]
  def change
    if ActiveRecord::Base.connection.column_exists?(:products, :description)
      remove_column :products, :description
    end
  end
end

To simplify these conditions, now Rails has added support for if_exists/if_not_exists on remove_column/add_column in migrations.

With Rails 6.1

Adding a column

class AddDescriptionToProduct < ActiveRecord::Migration[6.1]
  def change
    add_column :products, :description, :text, if_not_exists: true
  end
end

If the column doesn’t exist then the above migration will add the column otherwise it won’t raise an error.

-- add_column(:products, :description, :text, {:if_not_exists=>true})
   -> 0.0017s

Removing a column

class RemoveDescriptionFromProduct < ActiveRecord::Migration[6.1]
  def change
    remove_column :products, :description, if_exists: true
  end
end

Similarly, the above migration will remove the column if it exists, else it’ll run the migration without error.

-- remove_column(:products, :description, {:if_exists=>true})
   -> 0.0153s

Sunday, 09. February 2020

Riding Rails

This week in Rails - PostgreSQL 11 partitioned indexes support and more!

Hello, this is Greg, bringing you the latest news about Ruby on Rails!

20 contributors to Rails in past week

There have been 20 contributors to Rails past week! 

Default HSTS max-age directive to 2 years

The new recommendation for the HSTS max-age directive is 2 years, and that’s what Rails defaults to from now on.

Add support for partitioned indexes in PostgreSQL 11+

This pull request adds support to retrieve partitioned indexes when asking for indexes in a table. 

Add a fallback database config when loading schema cache

The schema cache defaults to loading the ‘primary’ database config, however, if an app doesn’t have a db config with a spec name of ‘primary’ the filename lookup will  blow up. This pull request adds a fallback for this case.

That’s it for this week, till next time! 

Friday, 07. February 2020

OneBitCode

Apertando o Start – DesBugando #1 [Podcast]

 

O podcast que veio para exterminar os bugs do seu código e te guiar pelo maravilhoso mundo da programação!
DesBugando #1 – Apertando o Start com:
   • Leonardo Scorza – https://onebitcode.com
   • Lucas Caton – https://www.lucascaton.com.br
   • Jackson Pires – https://videosdeti.com.br

O post Apertando o Start – DesBugando #1 [Podcast] apareceu primeiro em OneBitCode.

Wednesday, 05. February 2020

Fast Ruby Blog

Gemifying your style guide to DRY your CSS

At Ombu Labs we like to follow a style guide to drive our own products. A style guide is a document that provides guidelines for the way your brand should be presented from both a graphic and language perspective. You can see Fast Ruby's style guide at this link.

Since we have a few applications in place and it's important to make sure that they all use the same style, we need to ensure that they will all inherit the same CSS files. One way to do this is to copy the above style guide and paste it inside all of our apps, but this would end up causing a lot of duplicated code. If we decided to change the font-style, for example, we would need to change it in all apps individually.

Something else we are super fans of at Ombu Labs is to follow good code and development practices. One of our favorites is the DRY (Don’t Repeat Yourself) principle, which states that duplication in logic should be eliminated via abstraction. So to avoid the duplicated code here, we decided to create a gem to encapsulate our style guide and to be bundled in all of our products.

In this article, I'll show you how we did it!

Creating the Gem

The first step is to create the gem. Bundler has a command to create the skeleton of the Gem.

bundle gem fastruby-styleguide

Go to the created .gemspec file and add the info about the new gem and also a few dependencies:

# fastruby-styleguide.gemspec
require_relative 'fastruby/styleguide/version'

Gem::Specification.new do |spec|
  spec.name           = 'fastruby-styleguide'
  spec.version        = Styleguide.gem_version
  spec.authors        = ['OmbuLabs']
  spec.email          = ['hello@ombulabs.com']

  spec.summary        = 'Style Guide for all Fast Ruby products'
  spec.homepage       = 'https://github.com/fastruby/styleguide'

  # Rails
  spec.add_dependency 'rails', '>= 5.2.1'
  spec.add_dependency 'sass-rails', '>= 5.0'
  # Jquery
  spec.add_dependency 'jquery-rails', '>= 4.3.0'
  # Bootstrap
  spec.add_dependency 'bootstrap-sass', '>= 3.4.0'
  # Popper
  spec.add_dependency 'popper_js', '>= 1.14.5'
  spec.add_dependency 'material_design_lite-sass', '>= 1.3.0'
end

By default the gem is created as a module but we need to turn it into an engine, because we need to be able to integrate the gem code into any Rails application. In case you want to understand more about the Rails engine, check this link.

So, let's change the lib/fastruby-styleguide.rb

module Fastruby
  module Styleguide
    class Engine < ::Rails::Engine
    end
  end
end

Now we need to copy all the assets for the vendor/assets folder. The project structure will be like this:

Folder structure

Go back to the .gemspec file and make sure that the new files will be loaded adding the following line:

spec.files = Dir['vendor/**/*']

And this is it! The gem is ready to be used.

Adding the gem to a project

To use our new created gem we need to bundle it like any other gem. So add this line to your application's Gemfile:

gem 'fastruby-styleguide'

And then execute:

$ bundle install

The next step is to require the new gem assets into the application:

If using SASS, in application.scss, add:

@import "fastruby/styleguide";

And in application.js add:

//= require fastruby/styleguide

Now start the application and the gem assets should be available to use.

You can see our style guide code in this GitHub repository: https://github.com/fastruby/styleguide.

Conclusion

As developers, we are always thinking about the best way to organize our code and follow good practices, so here is our solution to share the same assets code amongst our several products. If you're facing the same issue, I hope that this is useful.

Extra

Want to see our style guide applied? We're proud to present our projects: Fastruby.io and Audit Tool.

Tuesday, 04. February 2020

RubyGuides

7 Major Differences Between Java & Ruby

How do you move from Java to Ruby?

In this guide, you’ll learn what are the major differences between the two languages to help you make the jump.

It’s great for the many people looking to move from Java/C# to Ruby.

But…

If you’re just curious about the differences, then this is also for you.

Let’s do this!

Static Typing vs Dynamic Typing

Typing style is the biggest & most notable difference when looking at code for either programming language.

Here’s the situation:

  • Java uses static typing
  • Ruby uses dynamic typing

What does that mean, exactly?

Typing refers to how variables & method arguments work.

A strongly (or statically) typed language has to declare what types (classes) every variable can accept.

It looks like this:

int n = 1;

Where int is the type, in this case, Integer.

Why is that useful?

Because if you try to assign any other type of object to this variable, the compiler will raise an error & your program won’t even get to run.

This means that you’ll always know what kind of object you’re working with.

With the disadvantage of being less flexible.

In contrast…

Dynamic typing is all about flexibility, variables aren’t tied to a particular type, so the type can change.

Here’s an example:

n = 1
n = "abc"

The downside is that you may run into more errors if your code is sloppy & you don’t get as much when reading code.

That’s one of the major differences!

More in the next section.

Syntax: Simple Things & Boilerplate

Java is the king of boilerplate.

Boilerplate is all that “setup” code that you have to add just to make your code syntactically valid.

As a result…

Java makes SIMPLE things complicated!

Hello World in Java:

class Hello {
  static public void main() {
    System.out.println("Hello World");
  }
}

Then you have to compile this into a class file & run it.

Two steps!

(IDEs help with this.)

Another difference is that expressions in Java end with a semicolon, which is not required in Ruby. You can also leave out parenthesis in Ruby, most of the time.

Hello World in Ruby:

puts "Hello World"

Yep.

That’s it.

It seems like Ruby results in cleaner code. Agree?

On top of that…

You can run this code with a single command from a terminal!

No compilation step needed.

Playing With Code: Built-In REPL

Ruby comes with irb, which makes testing out a bit of code really quick.

You don’t even need to save a file.

Or open an editor!

IRB Example

Java doesn’t have this built-in, at least it doesn’t until Java 9.

File Names & File Organization

Java forces a specific file naming structure & organization.

For example:

If you have a (public) class named Hello, the file name MUST be Hello.java.

This isn’t the case in Ruby.

Also, we don’t have the concept of a private class in Ruby.

Exception Handling

Java has two types of exceptions:

  • Checked exception
  • Unchecked exception

The difference?

You HAVE to handle checked exceptions!

Or your program won’t even run.

Now:

Ruby has only one type of exception.

You can handle it if you want, the interpreter won’t complain about it, but it may end up crashing your program.

Compiler & Language Licensing

Lastly, let’s talk about licensing.

This isn’t usually a problem with programming languages.

Why?

Because they’re open source, with one person as the head maintainer & designer.

Java is different here.

It to a big corporation (Oracle) & that has implications on it’s licensing.

In fact:

There are two versions of the Java runtime, “Oracle JDK”, which (if I understand correctly) starting with version 9 is a commercial product.

Then you have “Open JDK”, which also belongs to Oracle.

But it has an open-source license.

Library & Code Distribution

Another MAJOR difference, one of my favorites (also Matz’s favorite) about Ruby is RubyGems.

It makes distributing libraries (like HTTP clients) much easier.

Because:

  • There is a central repository
  • It’s integrated into the language
  • Many open-source gems are available & easy to find

As far as I know, Java doesn’t have anything close to this, so this is a major win for Ruby.

Summary

You’ve learned about the major difference between Java & Ruby. Including things like static typing, boilerplate, and file naming.

Now you’re better prepared to understand Ruby so you can make the correct decision for you.

Thanks for reading! 🙂

The post 7 Major Differences Between Java & Ruby appeared first on RubyGuides. Don't miss your free gift here :)


Ruby Conferences 'n' Camps in 2020 - What's Upcoming?

RubyNess @ Inverness, Scotland, United Kingdom Announced

Conferences 'n' Camps

What's News? What's Upcoming in 2020?

RubyNess
Jul/16+17 (2d) Thu+Fri @ Inverness, Scotland, United Kingdom • (Updates)

See all Conferences 'n' Camps in 2020».


Ruby by the Bay (Ruby for Good, West Coast Edition) @ Marin Headlands (near San Francisco), California, United States Announced

Conferences 'n' Camps

What's News? What's Upcoming in 2020?

Ruby by the Bay (Ruby for Good, West Coast Edition)
Apr/3-6 (4d) Fri-Mon @ Marin Headlands (near San Francisco), California, United States • (Updates)

See all Conferences 'n' Camps in 2020».


The Official BigBinary Blog | BigBinary

Rails 6.1 introduces class_names helper

This blog is part of our Rails 6.1 series.

Rails 6.1 adds class_names view helper method to conditionally add CSS classes. class_names helper accepts String, Hash and Array as arguments and returns string of class names built from arguments.

Before Rails 6.1, conditional classes were added by using conditional statements. Let’s take an example of adding an active class to navigation link based on the current page.

Rails 6.0.0

<li class="<%= current_page?(dashboards_path) ? 'active' : '' %>">
  <%= link_to "Home", dashboards_path %>
</li>

Rails 6.1.0

>> class_names(active: current_page?(dashboards_path))
=> "active"

# Default classes can be added with conditional classes
>> class_names('navbar', { active: current_page?(dashboards_path) })
=> "navbar active"

# class_names helper rejects empty strings, nil, false arguments.
>> class_names(nil, '', false, 'navbar' {active: current_page?(dashboards_path)})
=> "navbar active"
<li class="<%= class_names(active: current_page?(dashboards_path)) %>">
  <%= link_to "Home", dashboards_path %>
</li>

Check out the pull request for more details on this.

Monday, 03. February 2020

Code With Jason

What kinds of Rails tests I write and what kinds I don’t

The challenge of deciding what kinds of tests to write

There are a lot of different kinds of tests a developer could possibly write. In RSpec there are model specs, feature specs, view specs, helper specs, routing specs, controller specs, and request specs. Do you need to write all these different types of tests? If not, which ones should you skip?

The two types of tests I write

In my Rails apps I usually only write two kinds of tests. To use framework-agnostic language, I write model tests and acceptance tests. To use RSpec-specific language, I write model specs and feature specs. Let’s talk about each of these two in a little more detail.

Model tests are relatively isolated. A model test will test the behavior of a particular Ruby class in my Rails application, e.g. the Customer model. Model tests are what give me confidence that my features are working at a fine grain. (I often use the terms “class” and “model” somewhat interchangeably since a Rails model takes the form of a Ruby class.) For example, if I have a method that returns the grand total of the payments a certain customer has made, I’ll write a model test for that method.

What about acceptance tests? Whereas model tests are relatively isolated, acceptance tests are the opposite; they’re relatively integrated. An acceptance test exercises the whole stack, browser and all. Acceptance tests give me confidence that everything works together. For example, if I have CRUD functionality for a Customer resource, I’ll write acceptance tests for creating a customer, updating a customer, and deleting a customer.

A note on terminology

As of the time of this writing there not complete consensus in the testing world on testing terminology (and I don’t expect this to change anytime soon). For example, some developers might consider the terms end-to-end-test, integration test, and acceptance test to refer to basically the same thing, whereas other developers might consider these three terms to refer to three completely distinct types of tests.

What does this mean for you in terms of your testing journey? It means that if you, for example, see a reference to end-to-end tests that doesn’t square with your understanding of the term end-to-end tests, that doesn’t necessarily mean that your understanding is mistaken, it just means that not everyone in the testing world talks about the same concepts using the same exact terms. I think understanding this communication challenge is helpful since otherwise one might get distracted by all the apparent discrepancies.

Testing terminology + RSpec

How do model tests and acceptance tests map to RSpec? Model tests—or model “specs” in RSpec terminology—are the tests that live in the spec/models directory. Nothing but bare RSpec is needed in order to write model specs. What the outside world calls acceptance tests (or again, integration tests or end-to-end tests) are known in RSpec as feature specs. Since feature specs exercise features via the browser, the Capybara library (a library that allows us to manipulate the browser using Ruby) is necessary.

Why, by the way, is an RSpec test called a “spec” instead of a “test”? The idea is that each RSpec file is an “executable specification”. Each test case is actually called an example in RSpec terminology, an example of how that feature is supposed to behave. A set of examples makes up a spec. Personally, I’m willing to play along with this terminology to some extent, but I frankly find it kind of annoying. You’ll find that most of the time I say “test” instead of “spec” or “example”.

The types of tests I don’t write

I typically don’t write view specs, routing specs, or request specs/controller specs. Let’s discuss each of these.

Request specs/controller specs

First, a bit of terminology: request specs and controller specs are two very slightly different things, although for our purposes we can treat the two terms as meaning the same thing. A close enough approximation to the truth is that “controller spec” is the old name and “request spec” is the new name.

I usually don’t write controller/request specs (although I do sometimes). The reason is that I try to have as little code in my controllers as possible. For the most part, my controllers don’t do anything, they just call model objects that do things. Any code that might have been covered by a controller/request spec, I prefer to cover via a feature spec.

View specs, routing specs

I don’t write view specs and I don’t believe I’ve ever encountered anyone who does. The reason is that I’ve never been able to conceive of how a view spec could have value that a feature spec doesn’t already provide.

I also don’t write routing specs, and for the same reason. I find them to be redundant to feature specs.

Conclusion

If you’re just getting started with Rails/RSpec testing and you’re wondering which types of tests to focus on, I would recommend focusing on model specs and feature specs.

The post What kinds of Rails tests I write and what kinds I don’t appeared first on Code with Jason.


Lucas Caton

Instalação do Ruby & do NodeJS no Windows, usando WSL e ASDF

Abaixo você encontra os comandos executados no vídeo:

No PowerShell:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

No terminal do Ubuntu (WSL):
sudo apt update
sudo apt install git automake autoconf libreadline-dev libncurses-dev libssl-dev libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev unzip curl zlib1g-dev sqlite3 libsqlite3-dev
git clone https://github.com/asdf-vm/asdf.git ~/.asdf
cd ~/.asdf
git checkout "$(git describe --abbrev=0 --tags)"

echo -e '\n. $HOME/.asdf/asdf.sh' >> ~/.bashrc
echo -e '\n. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc
source ~/.bashrc
asdf --version
asdf plugin-add ruby
asdf install ruby 2.6.5
asdf global ruby 2.6.5
ruby -v
asdf plugin-add nodejs
bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring
asdf install nodejs 12.14.1
asdf global nodejs 12.14.1
node -v
gem install rails
rails -v
npm install --global yarn
yarn -v

Criando um projeto Rails para garantir que tudo está funcionando:
rails new todo
cd todo
rails s

Abra o navegador e acesse a página localhost:3000.


The Life of a Radar

ROM + Dry Showcase: Part 3 - Testing

This is the 3rd part of a 4 part series covering the rom-rb and dry-rb suites of gems.

In this 3rd part, we're going to look at how we can test the application that we've built so far. In particular, we'll test three classes:

  • The contract -- to ensure it validates input correctly
  • The repository -- to ensure we can insert data into our database correctly and that we could find data once it is inserted
  • The transaction -- to ensure that we can process the whole transaction correctly

When we get up to the transaction part, we'll see how we can use one more feature of dry-auto_inject to stub out the repository dependency in this particular test. Why would we want to stub out this dependency? Because we already have tests that make sure that our repository works! We don't need to test it again a second time in the transaction class.

Let's get started!

Adding RSpec

First things first! We will need to set up the RSpec testing framework, and a gem called database_cleaner-sequel. The database cleaner gem will ensure that our database is kept pristine across the different tests in our application. If we have data "leaking" across tests, that data may influence the outcome of other tests.

Let's add these gems to our Gemfile now:

group :test do
  gem 'rspec'
  gem 'database_cleaner-sequel'
end

We've put these gems in a "test" group, as we will not want them installed when we deploy to production.

Then we'll install these gems locally with bundle install.

Next up, we can initialize RSpec by running:

bundle exec rspec --init

This will create us a spec directory with a file called spec_helper.rb in it. Here's that file with the comments removed and with the database cleaner configuration added:

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups

  config.before do
    DatabaseCleaner.clean
  end
end

require_relative '../config/application'
Bix::Application.finalize!

require 'database_cleaner/sequel'
DatabaseCleaner.strategy = :truncation

At the bottom of this file, I've also added two lines to require config/application.rb and to finalize our application. This will ensure that by the time the tests run everything for our application has been loaded.

At the bottom of the configure block, we clean the database to ensure that each and every test starts out with a completely empty database. How that database gets cleaned is defined by the final two lines in this file: it's a truncation strategy meaning that each table in our database will be truncated before the test runs.

That's all the setup that we'll need to do here. Let's write our first couple of tests for the contract.

Testing the contract

When we go to test a contract, we want to be sure that both the valid and invalid paths are covered effectively. Let's start off with the invalid paths first and we'll finish with the valid one. We'll create a new file in spec/contracts/users/create_user_spec.rb:

require 'spec_helper'

RSpec.describe Bix::Contracts::Users::CreateUser do
  context "requires first_name" do
    let(:input) do
      {
        last_name: "Bigg",
        age: 32
      }
    end

    let(:result) { subject.call(input) }

    it "is invalid" do
      expect(result).to be_failure
      expect(result.errors[:first_name]).to include("is missing")
    end
  end

  context "requires last_name" do
    let(:input) do
      {
        first_name: "Ryan",
        age: 32
      }
    end

    let(:result) { subject.call(input) }

    it "is invalid" do
      expect(result).to be_failure
      expect(result.errors[:last_name]).to include("is missing")
    end
  end
end

In both of these tests, we're setting up some invalid input for the contract. And also in both of these tests, we're asserting that the contract shows us an error indicating either the first_name or last_name fields are missing.

We can run this test with bundle exec rspec. When we do this, we'll see that the contract is working as intended:

2 examples, 0 failures

This is good to see, and will now provide us with a safety net. If someone was to delete one of the lines from the contract perhaps accidentally, then our tests would catch that.

Let's add another test for the happy path, the path of successful validation, to this file too:

RSpec.describe Bix::Contracts::Users::CreateUser do
  context "given valid parameters" do
    let(:input) do
      {
        first_name: "Ryan",
        last_name: "Bigg",
        age: 32
      }
    end

    let(:result) { subject.call(input) }

    it "is valid" do
      expect(result).to be_success
    end
  end

  ...

In this test, we provide all the correct values for the input and therefore our contract should be successful. Let's run the tests again and see:

3 examples, 0 failures

Great!

We're able to test our contract just like a standard Ruby class. We initialize the contract, and then depending on the input it is given, the contract with either succeed or fail.

Testing the repository

To test the repository, we can take the same kind of path. For this repository's tests, we need to assert that the create method for Bix::Repos::UserRepo does a few things. What things? Well, let's look at our code for the repository:

module Bix
  module Repos
    class UserRepo < ROM::Repository[:users]
      include Import["container"]

      struct_namespace Bix

      commands :create,
        use: :timestamps,
        plugins_options: {
          timestamps: {
            timestamps: %i(created_at updated_at)
          }
        }

      def all
        users.to_a
      end
    end
  end
end

The repository has a create method and with this method we need to make sure:

  • The method returns a Bix::User object -- because struct_namespace has configured the repository to use the Bix namespace for all structs.
  • That the Bix::User object returned has an id attribute -- this ID is automatically set by the database
  • That both created_at and updated_at are set automatically -- based on how we've configured the command at the top of the repository.

The repository also has an all method, and just to be thorough we can add a simple test for this method to assert that Bix::User objects are returned.

Let's add the tests for the create method first. We'll create a new file at spec/repos/user_repo_spec.rb for these tests:

require "spec_helper"

RSpec.describe Bix::Repos::UserRepo do
  context "#create" do
    it "creates a user" do
      user = subject.create(
        first_name: "Ryan",
        last_name: "Bigg",
        age: 32,
      )

      expect(user).to be_a(Bix::User)
      expect(user.id).not_to be_nil
      expect(user.first_name).to eq("Ryan")
      expect(user.last_name).to eq("Bigg")
      expect(user.created_at).not_to be_nil
      expect(user.updated_at).not_to be_nil
    end
  end
end

This test asserts that when we call create on our repository that it will return a Bix::User object and that the object has values for id, first_name, last_name, created_at and updated_at.

If we run this test with bundle exec rspec, we'll see that it passes:

4 examples, 0 failures

Good! We've now asserted that at least the struct_namespace and commands methods from our repository are working as they should. If a mistake was to be made, like accidentally removing the struct_namespace method from the repository, our test would break:

  1) Bix::Repos::UserRepo#create creates a user
     Failure/Error: expect(user).to be_a(Bix::User)
       expected #<ROM::Struct::User ...> to be a kind of Bix::User

The issue with this repository returning a ROM::Struct::User object instead of a Bix::User object is that the ROM::Struct::User objects will not have access to any of Bix::User's methods, like full_name. If we had this mistake in our application, and we tried using User#full_name then our application would break. This demonstrates why it's important to have tested that struct_namespace is working.

Let's add another quick test to our repository to test all:

context "#all" do
  before do
    subject.create(first_name: "Ryan", last_name: "Bigg", age: 32)
  end

  it "returns all users" do
    users = subject.all
    expect(users.count).to eq(1)
    expect(users.first).to be_a(Bix::User)
  end
end

This test uses create to setup a user in our database, and then asserts that when we call all we get a user back.

If we run this test, we'll see that it's already working:

5 examples, 0 failures

This means that our all method now has some test coverage. If this method was to break somehow, then our test would indicate that the method was faulty and then we wwould know to fix it.

Testing the transaction

So far, our testing of contracts and repositories has been very straightforward Ruby class tests. We have relied on subject from RSpec which is a method that behaves like this:

def subject
  <described class>.new
end

Now we're going to look at how to test a transaction, and here's where things are going to get more interesting. Rather than relying on RSpec's own subject, we're going to define our own. And when we define our own, we're going to use a feature of dry_auto-inject, called dependency injection. This feature will allow us to inject a stubbed repository into our transaction, so that we don't have to hit the database for our transaction's test.

Not hitting the database means that we will save time on this test: there's no need to make a request to a system outside of our Ruby code, and that'll also mean that database_cleaner will not need to clean anything from the database. Ultimately, by injecting the repository dependency into our application's transactions when we're testing them means that we can have fast transcation tests.

Let's look at how to do this by creating a new file at spec/transactions/users/create_user_spec.rb:

require 'spec_helper'

RSpec.describe Bix::Transactions::Users::CreateUser do
  let(:user_repo) { double("UserRepo") }
  let(:user) { Bix::User.new(id: 1, first_name: "Ryan") }

  subject { described_class.new(user_repo: user_repo) }

  context "with valid input" do
    let(:input) do
      {
        first_name: "Ryan",
        last_name: "Bigg",
        age: 32,
      }
    end

    it "creates a user" do
      expect(user_repo).to receive(:create) { user }
      result = subject.call(input)
      expect(result).to be_success
      expect(result.success).to eq(user)
    end
  end
end

In this test, we define our own subject block, which will override RSpec's default. We inject the user_repo dependency into the transaction object by passing a user_repo key in the new method. This works because dry-auto_inject re-defines initialize for classes when we use this syntax:

include Import[
  "contracts.users.create_user",
  "repos.user_repo"
]

By default, dry-auto_inject will load the contract class Bix::Contracts::Users::CreateUser, as well as the repo class Bix::Repos::UserRepo and provide them to the class through the create_user and user_repo methods automatically. The keys that we provide to Import[] here match the keys that are automatically defined by dry-system when it automatically registers the components for our application.

If we want to swap in something else for either the contract or the repository, we can do that by passing in a key matching the name (either create_user or user_repo) when we initialize this class. Just like we do in our test!

If we wanted to do this ourselves, without any sort of dry gem magic, it would look like this:

attr_reader :user_repo, :create_user

def initialize(
  user_repo: Bix::Repos::UserRepo.new,
  create_user: Bix::Contracts::Users::CreateUser.new
)
  @user_repo = user_repo
  @create_user = create_user
end

As we can see, by using dry-auto_inject along with dry-system we get to save a lot of typing.

Let's look at that test again:

require 'spec_helper'

RSpec.describe Bix::Transactions::Users::CreateUser do
  let(:user_repo) { double(Bix::Repos::UserRepo) }
  let(:user) { Bix::User.new(id: 1, first_name: "Ryan") }

  subject { described_class.new(user_repo: user_repo) }

  context "with valid input" do
    let(:input) do
      {
        first_name: "Ryan",
        last_name: "Bigg",
        age: 32,
      }
    end

    it "creates a user" do
      expect(user_repo).to receive(:create) { user }
      result = subject.call(input)
      expect(result).to be_success
      expect(result.success).to eq(user)
    end
  end
end

The test asserts that when we use subject.call that the repository receives the create method once. We've stubbed this method to return a Bix::User object, and that's what we'll see when we call result.success at the end of the test.

Let's run this test and we'll see how it goes:

6 examples, 0 failures

Success! We're able to test our transaction without it hitting the database at all. This means that our transaction test is isolated from the database, leading to it being quick. While we only have one transaction test now, as this application grows and we add further transaction tests this quickness will quickly pile-up to a big benefit.

There's also another benefit of this isolation: if we had database constraints then we would have to cater for those in this test. Imagine for instance that when we created users that they had to be associated with a "Group" and that Groups had to be associated with an "Account". In a normal application to test such a thing, we would need to create three separate objects our database: an account, a group, and a user.

For one test, it won't matter too much. But if accounts, groups and users are the core of our application, it would quickly stack up to lots of database calls. By stubbing out the user repository dependency while testing this transaction, we have isolated that test from any database concern. A better place to test that sort of database concern would be in the repository test, anyway.

To finish up, let's add one more test for what happens when this transaction fails due to invalid input:

context "with invalid input" do
  let(:input) do
    {
      last_name: "Bigg",
      age: 32,
    }
  end

  it "does not create a user" do
    expect(user_repo).not_to receive(:create)
    result = subject.call(input)
    expect(result).to be_failure
    expect(result.failure.errors[:first_name]).to include("is missing")
  end
end

This input is missing a first_name key, and so our transaction should fail. This means that the user_repo should never receive a create method, because our transaction will only call that if the validate step passes. When the validation fails, we would expect the result from this transaction to be a failure, and that failure to contain errors indicating what went wrong.

When we run this test with bundle exec rspec, we'll see it pass:

7 examples, 0 failures

Summary

In this 3rd part of the ROM and Dry showcase, we've seen how easy it is to add tests to our application to ensure that the individual parts of the application are working.

We saw that in order to test a contract and a repository, we can initialize either class and call the methods we want to test. There's nothing particularly special that we've had to do to test these classes; we treat them like the plain Ruby classes they are.

When testing the transaction, we've chosen to isolate those tests from the database by injecting a stubbed UserRepo object in place of the real thing. This isolation will mean that our tests will not have to concern themselves with setting up database state -- for instance, if we had foreign key constraints -- and over time it will mean that our transaction tests will be lightning fast.

In the next part of this series, we'll add the final piece of our application to our stack: a way to make HTTP requests. And we'll definitely be adding tests for this too!

Sunday, 02. February 2020

The Life of a Radar

ROM + Dry Showcase: Part 2 - Validations & Transactions

This is the 2nd part of a 4 part series covering the rom-rb and dry-rb suites of gems.

In this part, we're going to look at how to add data validation to our application. Our application currently has a way of creating users, but there's nothing preventing those users being created without their first_name or last_name set. In this part, we'll add some code to validate that both of these fields are set. We're going to add this code by using a gem called dry-validation.

When we've added this code, it's going to sit apart from the repositories and relations that we've built already, and we will need a way of connecting these pieces. The way that we will connect these pieces is through the dry-monads gem.

When we're done here, we'll have a class that encapsulates all the actions of creating a user:

  1. Validates first_name and last_name are present
  2. If they aren't present, returns an error.
  3. If they are present, the user data is persisted to the database

We'll call this class a transaction, as it will contain all the logic for performing a particular transaction with our system; the transaction of creating a new user.

If you'd like to see the code for this application, it's at github.com/radar/bix, and each part of this series has its own branch.

Let's begin!

Adding validations

Validations are a key part of any application. We need to make sure that before data is stored in our database that it is valid. In our currently very small application, we so far have just one type of data: users. Still, in this tiny application it doesn't really make much sense to create users that don't have a name. In this section we're going to add a class that will validate a particular input for user data is valid.

To start with, we'll need to add the dry-validation gem to our Gemfile:

gem 'dry-validation', '~> 1.4'

Next up, we'll need to install the gem:

bundle install

We'll need to require this gem somewhere too, so that it is loaded in our application. To load this gem and other gems that we'll add in the future, we'll create a new file at system/boot/core.rb.

Bix::Application.boot(:core) do
  init do
    require "dry-validation"
  end
end

This new file will include any sort of setup logic that we will need for the core part of our application. This is going to be everything that we'll need when running the plain Ruby code for our application. We have a db.rb and persistence.rb file in this same directory that contains logic for anything we want to do with a database. n the last part of this guide, we'll add a fourth file in this directory called web.rb and that file will contain setup logic for anything to do with handling web requests.

The dry-validation gem allows us to create classes to encapsulate validation logic, and this gem uses another dry-rb gem under the hood called dry-schema

These classes are called contracts. We'll create our first contract at lib/bix/contracts/users/create_user.rb:

module Bix
  module Contracts
    module Users
      class CreateUser < Dry::Validation::Contract
        params do
          required(:first_name).filled(:string)
          required(:last_name).filled(:string)
          optional(:age).filled(:integer)
        end
      end
    end
  end
end

This class defines a contract that says that when we're creating users, there has to be at least two parameters -- first_name and last_name, and they both have to be filled (present) strings. This contract also says that an age parameter is optional, but when it's specified it's an integer. Let's try using this contract now in bin/console:

create_user = Bix::Contracts::Users::Create.new
result = create_user.call({})

To use this contract, we need to initialize a new object from the class and then use the call method on that new object. The argument that we pass it are the parameters for the contract, which in this case is just an empty Hash.

When we call this contract, we see the validation errors returned:

=> #<Dry::Validation::Result{} errors={:first_name=>["is missing"], :last_name=>["is missing"]}>

The returned object is a Result object, and with that result object we can determine if the validation was successful by calling the success? method:

result.success?
# => false

If we wanted to display these error messages (for example, as feedback to a user) we could call:

result.errors.to_h
=> {:first_name=>["is missing"], :last_name=>["is missing"]}

Let's look at what happens when we pass valid data, but with a twist: all of our values are strings. This is the kind of data you would get from a form submission through a web application:

create_user = Bix::Contracts::Users::Create.new
result = create_user.call(first_name: "Ryan", last_name: "Bigg", age: "32")
=> #<Dry::Validation::Result{:first_name=>"Ryan", :last_name=>"Bigg", :age=>32} errors={}>
result.success?
# => true

Great, our contract is correctly validating input! What's interesting to note here is that the age parameter is being correctly typecast from a String to an Integer. This is because we have defined that field to be an integer in our contract:

module Bix
  module Contracts
    module Users
      class CreateUser < Dry::Validation::Contract
        params do
          required(:first_name).filled(:string)
          required(:last_name).filled(:string)
          optional(:age).filled(:integer)
        end
      end
    end
  end
end

If we pass data from a form submission through our contract before we work through it, the data will have all the correct types and we don't need to coerce that data when we're working with -- dry-validation has done that for us. After this point, our data will always be in the correct type.

Another thing to note with our new contract is that it will only return the specified fields. Extra fields will be ignored:

create_user = Bix::Contracts::Users::Create.new
result = create_user.call(first_name: "Ryan", last_name: "Bigg", age: "32", admin: true)
# => #<Dry::Validation::Result{:first_name=>"Ryan", :last_name=>"Bigg", :age=>32} errors={}>

The admin field doesn't appear here at all, even though we've specified it as an input to this contract.

So in summary, here's what we're given by using a dry-validation contract:

  • Validations to ensure fields meet certain criteria
  • Automatic type coercion of fields into their correct types
  • Automatic limiting of input to just the fields we have specified

Intro to Dry Monads

Now that we have a way to create user records (the Bix::Repos::User) and a way to validate that data before it gets into the database (Bix::Contracts::Users::Create), we can combine them to ensure data is valid before it reaches out database.

To do this combination, we could write a class like this:

class CreateUser
  def call(input)
    create_contract = Bix::Contracts::Users::Create.new
    result = create_contract.call(input)
    if result.success?
      user_repo = Bix::Repos::User.new
      user_repo.create(input)
    else
      result
    end
  end
end

From the start, this class doesn't look so bad. But if we added one more if condition or perhaps some code to send a "successful sign up" email to a user, this class would get longer and more complex.

To avoid that kind of complexity, the dry-rb suite of gems provides another gem called dry-monads. Among other things, this dry-monads gem provides us with a feature called "Do Notation". This feature will allow us to write our CreateUser class in a much cleaner way that will also allow for extensibility later on -- if we want that.

Let's add this gem to our Gemfile now:

gem 'dry-monads', '~> 1.3'

And we'll run bundle install to install it.

Next up, we will need to require this gem in system/boot/core.rb:

Bix::Application.boot(:core) do
  init do
    require "dry-validation"
    require "dry/monads"
    require "dry/monads/do"
  end

  start do
    Dry::Validation.load_extensions(:monads)
  end
end

We've changed core.rb here to require dry/monads and dry/monads/do. The second file will give us access to Dry Monad's Do Notation feature. We've added a start block here, which will run when our application is finalized. This will add an extra to_monad method to our validation results. We'll see this used in a short while.

Before we get there, we need to talk about two things. One is called the Result Monad, and the other is the Do Notation.

Result Monad

The Result Monad is a type of object that can represent whether an action has succeeded or failed. Where it comes in handy is when you have a chain of actions that you might want to stop if one of those things goes wrong. For instance, in the above code when the user is invalid, we want the code to not persist the user to the database.

To do this with dry-monads, we would return one of two types of the result monad, a Success or Failure. Here's a flowchart showing what would go on when we use a Result monad:

Result monad diagram

Here we have a "Create User" action that has two steps: a "Validate User" and a "Persist User" step. When our "Create User" action receives some parameters, it passes them to the "Validate User" step. When this step runs, there can be one of two results: success or failure.

When the validation succeeds, that step returns a Success result monad which will contain the validated (and type-casted!) parameters.

If the validation fails, the step returns a Failure result monad. This monad contains the validation errors.

When our code sees a Failure Result Monad returned, it will not execute the remaining steps. In the above diagram, the validation of a user must succeed before persistence happens. Just like in the earlier code we wrote too.

Do Notation

The Result Monad is used in conjunction with that other feature of dry-monads I mentioned earlier: Do Notation. Let's take the above CreateUser class and re-write it using dry-monads' Do Notation. We'll put this class at lib/bix/transactions/users/create_user.rb:

module Bix
  module Transactions
    module Users
      class CreateUser
        include Dry::Monads[:result]
        include Dry::Monads::Do.for(:call)


        def call(input)
          values = yield validate(input)
          user = yield persist(values)

          Success(user)
        end

        def validate(input)
          create_contract = Contracts::Users::Create.new
          create_contract.call(input).to_monad
        end

        def persist(result)
          user_repo = Bix::Repos::UserRepo.new
          Success(user_repo.create(result.values))
        end
      end
    end
  end
end

This code is a bit longer than the code we had previously. However, it comes with a few benefits. The first of these is that each step is clearly split out into its own method.

The call method here is responsible for ordering the steps of our transaction. It takes our initial input for this transaction and runs it through the validator. All of that validation logic is neatly gathered together in the validate method:

def validate(input)
  create_contract = Contracts::Users::Create.new
  create_contract.call(input).to_monad
end

In this method, we use our contract that we built earlier. When we call this contract, it will return a Dry::Validation::Result object. To use this in conjunction with dry-monads' Do Notation, we need to convert this object to a Result Monad. We do this by calling to_monad on the result.

If the validation succeeds, we'll get back a Success(validated_input) result monad, otherwise a Failure(validation_result) result monad will be returned.

If it fails at this point, the transaction will stop and return the validation failure.

If it succeeds however, the transaction to the next step: create_user:

def create_user(result)
  user_repo = Bix::Repos::UserRepo.new
  Success(user_repo.create(result.values))
end

This step takes a result argument, which will be the validated_input returned from our validation step. We then initialise a new repo, and use that to create a user, taking the result.values. These values will be the validated and type-casted values from the validation's result.

Let's try using this class now in bin/console:

create_user = Bix::Transactions::Users::CreateUser.new
result = create_user.call(first_name: "Ryan", last_name: "Bigg", age: 32)
# => Success(#<Bix::User id=4 first_name="Ryan" last_name="Bigg" age=32 ...>)

When we use this transaction, it runs the validation and persistence steps for us. If everything goes well, like in the above example, then we get back a Success result monad.

Let's see what happens if the validation fails in this transaction:

create_user = Bix::Transactions::Users::CreateUser.new
result = create_user.call(first_name: "Ryan", last_name: "", age: 32)
# => Failure(#<Dry::Validation::Result{:first_name=>"Ryan", :last_name=>"", :age=>32} errors={:last_name=>["must be filled"]}>)

This time, we get back a Failure result monad, which is wrapping our Dry::Validation::Result. This will mean that the persistence won't happen at all.

Our transaction class so far has only two methods, but could be expanded out to include more. Perhaps we would want to send an email to the user to confirm that they've signed up?

Or what if we had a transaction class that handled account signup, where both an account and a user had to be created? A flowchart for that transaction class would look like this:

More complex transaction diagram

A transaction class is a great way of grouping together all these steps into small, discrete methods.

Handling success or failure

Let's now think about how we would actually use this CreateUser transaction class in a real context, something a bit more specialised than a simple bin/console session. For this section, we'll create a new file at the root of the Bix application, called transaction_test.rb. In this file, we'll put this content:

require_relative "config/application"

Bix::Application.finalize!

include Dry::Monads[:result]

input = {
  first_name: "Ryan",
  last_name: "Bigg",
  age: 32
}

create_user = Bix::Transactions::Users::CreateUser.new
case create_user.call(input)
when Success
  puts "User created successfully!"
when Failure(Dry::Validation::Result)
  puts "User creation failed:"
  puts result.failure.errors.to_h
end

This file starts out the same way as bin/console: we require config/application.rb and then "finalize" our application. This finalization step will load all the application's files and start all of the application's dependencies.

Next up, we include Dry::Monads[:result]. This gives us access to the Success and Failure result monad classes that we use at the end of this file.

Once we've set everything up, we define an input hash for our transaction, and the transaction itself. When we call the transaction, we can use a case to match on the outcome of the transaction. If it is successful, we output a message saying as much. If it fails, and the failure is a validation failure (indicated by the failure being a Dry::Validation::Result failure), we output the validation error messages.

Here we've seen a very simple way of handling the success or failure of a transaction. This code is very similar to how we would use the transaction in another context, such as a controller. The great thing about a transaction is that we aren't limited to using it just within a controller -- we could use it anywhere we pleased. This example is just a small one showing us how we could use it.

In Part 4 of this guide, we'll re-visit how to use this transaction in a different context.

Automatically injecting dependencies

Before we finish up this part of the showcase, I would like to demonstrate one additional piece of cleanup that we could do. Let's re-visit our transaction's code:

module Bix
  module Transactions
    module Users
      class CreateUser
        include Dry::Monads[:result]
        include Dry::Monads::Do.for(:call)

        def call(params)
          values = yield validate(params)
          user = yield persist(values)

          Success(user)
        end

        def validate(params)
          create_user = Bix::Contracts::Users::CreateUser.new
          create_user.call(params).to_monad
        end

        def persist(result)
          user_repo = Bix::Repos::UserRepo.new
          Success(user_repo.create(result.values))
        end
      end
    end
  end
end

This code looks pretty clean as it stands. But there's one extra thing we can do to make it even tidier, and that thing is to use dry-auto_inject's import feature. When we define things like the CreateUser contract or the UserRepo within our application, these classes are automatically registered with Bix::Application, because we've directed the application to auto_register things in lib. This happened over in config/application.rb:

require_relative "boot"

require "dry/system/container"
require "dry/auto_inject"

module Bix
  class Application < Dry::System::Container
    configure do |config|
      config.root = File.expand_path('..', __dir__)
      config.default_namespace = 'bix'

      config.auto_register = 'lib'
    end

    load_paths!('lib')
  end

  Import = Dry::AutoInject(Application)
end

We saw earlier that we could refer to the ROM container with the syntax include Import["container"] within our UserRepo class. Well, we can do the same thing with our contract and repository in this transaction class too.

Here's how we'll do it. At the top of the class, we'll put these two include lines:

module Bix
  module Transactions
    module Users
      class CreateUser
        include Dry::Monads[:result]
        include Dry::Monads::Do.for(:call)

        include Import["contracts.users.create_user"]
        include Import["repos.user_repo"]
...

By using include like this, we will be able to access our contract and repository in a simpler fashion. To do that, we can change our validate and persist methods in this transaction to this:

def validate(params)
  create_user.call(params).to_monad
end

def persist(result)
  Success(user_repo.create(result.values))
end

That's a lot cleaner, isn't it? We're now able to refer to the contract as simply create_user, and the repository as user_repo, without putting in those ugly namespaces into these methods. This syntax also more clearly defines the other class dependencies this transaction has, right at the top of the class. We don't need to scan through the class to figure out what they are anymore.

To make sure that things are working again, let's try running ruby transaction_test.rb again. If the input hash at the top of this file contains valid input, then we should see the successful message still:

User created successfully!

If this transaction class went on to use other classes from our application, we could import them with very little effort, thanks to the dry-system and dry-auto_inject gems.

Summary

In this 2nd part of the ROM and Dry showcase, we have used the dry-validation gem to add a contract to our application. A contract is a class that contains validation logic. It's a way of saying that incoming data must meet some criteria before our application can use it.

In the second half of this guide, we used dry-monads to define a transaction class within our application for creating users. This class is a collection of all the actions that our application would have to take to create a user. So far, there are only two of them: validate and persist. This class uses the contract to first validate the input, and then if that validation succeeds, the class will create a user in the database by using the repo.

In the final part of this guide, we used dry-auto_inject once more to automatically inject the repository and contract into our transaction class, allowing us to tidy up the code very slightly, but still noticeably.

In the next part, we're going to look at how we can test the parts of the application that we've built so far by using the RSpec testing framework. We'll also see another advantage of dry-auto_inject in this part.

Saturday, 01. February 2020

The Life of a Radar

ROM + Dry Showcase: Part 1 - Application + Database setup

The rom-rb and dry-rb sets of gems have come out in the last couple of years. These gems allow an alternative take on building a Ruby application, separate from Rails or Sinatra, or anything else like that.

In this series of blog posts, I am going to show you how to build a simple application that I'm calling "Bix" using some of these gems. By the end of this series, the application will:

This part will cover how to start building out an application's architecture. We'll also work on having this application speak to a database. For this, we'll use the following gems:

  • dry-system -- Used for loading an application's dependencies automatically
  • rom, rom-sql + pg -- We'll use these to connect to a database
  • dotenv -- a gem that helps load .env files that contain environment variables
  • rake -- For running Rake tasks, like migrations!

In this part, we will setup a small Ruby application that talks to a PostgreSQL database, by using the dry-system, rom, rom-sql and pg gems. At the end of this guide, we will be able to insert and retrieve data from the database.

If you'd like to see the code for this application, it's at github.com/radar/bix, and each part of this series has its own branch.

A word on setup costs

In these guides, you may get a sense that the setup of rom-rb and dry-rb libraries takes a long time -- maybe you'll think thoughts like "this is so easy in Rails!" These are normal and understandable thoughts. The setup of this sort of thing in Rails is easier, thanks to its generators.

However, Rails leads you into an application architecture that paints you into a corner, for reasons I explained in my "Exploding Rails" talk in 2018.

The setup of ROM and dry-rb things is harder, but leads you ultimately into a better designed application with clearer lines drawn between the classes' responsibilties.

It might help to think of it in the way my friend Bo Jeanes put it:

Setup cost is a cost that you pay once, whereas ease-of-application-maintenance is a cost that you pay every single day.

So in the long run, this will be better. I promise.

Installing Gems

To get started, we'll create an empty directory for our application. I've called mine bix. Inside this directory you will need to create a basic Gemfile:

source 'https://rubygems.org'

ruby '2.7.0'

gem 'dry-system'
gem 'rom'
gem 'rom-sql'
gem 'pg'

gem 'dotenv'
gem 'rake'

Once we have created that Gemfile, we'll need to run bundle install to install all of those dependencies.

Boot Configuration

Next up, we will create an environment for our application that will allow us to load dependencies of the application, such as files in lib or other dependencies like database configuration. We're going to use the dry-system gem for this.

Before we get to using that gem, let's create a file called config/boot.rb. This file will contain this code to load up our application's primary gem dependencies:

ENV['APP_ENV'] ||= "development"

require "bundler"
Bundler.setup(:default, ENV["APP_ENV"])

require "dotenv"
Dotenv.load(".env", ".env.#{ENV["APP_ENV"]}")

The first line of code sets up an APP_ENV environment variable. Our application will use this environment variable to determine what dependencies to load. For instance, when we're developing our application locally we may want to use development gems like pry. However, when we deploy the application to production, we will not want to use those gems. By setting APP_ENV, we can control what gems are loaded by our application.

The first block of code here will setup Bundler, which adds our gem dependencies' paths to the load path, so that we can require them when we need to. Note that Bundler.setup is different from Bundler.require (like in a Rails application) -- Bundler.setup only adds to the load path, and does not require everything at the beginning.

The two args passed here to Bundler.setup tell Bundler to include all gems outside of a group, and all gems inside of a group named after whatever APP_ENV is set to, which is development.

The first one that we require is dotenv, and that is just so we can load the .env or .env.{APP_ENV} files. When we're working locally, we'll want to have a .env.development file that specifies our local database's URL. Let's create this file now: .env.development:

DATABASE_URL=postgres://localhost/bix_dev

This file specifies the database we want to connect to when we're developing locally. To create that database, we will need to run:

createdb bix_dev

Application Environment Setup

To setup our application's environment and use this database configuration, we're going to use that dry-system gem. To do this, we'll create a new file called config/application.rb and put this code in it:

require_relative "boot"

require "dry/system/container"

module Bix
  class Application < Dry::System::Container
    configure do |config|
      config.root = File.expand_path('..', __dir__)
      config.default_namespace = 'bix'

      config.auto_register = 'lib'
    end

    load_paths!('lib')
  end
end

This code is responsible for loading our boot.rb file and defining a Bix::Application container. This container is responsible for automatically loading dependencies in from lib (when we have them!). This container is also responsible for handling how system-level dependencies for our application are loaded -- like how our application connects to a database.

To set that database connection up, we're going to create a new file over in system/boot/db.rb:

Bix::Application.boot(:db) do
  init do
    require "rom"
    require "rom-sql"

    register('db.config', ROM::Configuration.new(:sql, ENV['DATABASE_URL']))
  end
end

This system/boot directory is where we put system-level dependencies when using dry-system. This new file that we've created configures how our application defines its database connection.

To connect to the database, we need to use the rom and rom-sql gems. On the final line of init, we register a database connection to be used. This will pull the DATABASE_URL variable from the environment, which by default will load the one specified in .env.development.

Now that we have our database connection defined and our database itself created, we will need to create tables in that database. If this was a Rails app, we would use migrations to do such a thing. Fortunately for us, ROM "borrowed" that idea and so we can use migrations with ROM too.

To create migrations with ROM, we will need to create another file to define the Rake tasks, called Rakefile:

require_relative 'config/application'
require 'rom-sql'
require 'rom/sql/rake_task'

namespace :db do
  task :setup do
    Bix::Application.start(:db)
    ROM::SQL::RakeSupport.env = ROM.container(Bix::Application['db.config']) do |config|
      config.gateways[:default].use_logger(Logger.new($stdout))
    end
  end
end

This file loads the config/application.rb file that we created earlier and that will make it possible to require the other two files we use here.

In order to tell ROM's Rake tasks where our database lives, we're required to setup a Rake task of our own: one called db:setup. This configuration starts the system-level dependency :db by calling start on Bix::Application. This will run the code inside the init block defined within system/boot/db.rb. This init block registers a db.config with our application, and we can retrive that value by using Bix::Application['db.config'] here.

Inside this configuration, we configure something called the default gateway, which is the simply the default database connection that ROM has been configured with. We could configure multiple gateways, but we're only going to be using the one in this series. On this gateway, we tell it to use a new Logger instance, which will log SQL output for our Rake tasks.

Migrations

Like a lot of database frameworks, ROM also comes with migrations. We can use these to create the tables for our application.

To generate a migration with ROM, we can run:

rake "db:create_migration[create_users]"

This will create us a new file under db/migrate and it'll be almost empty:

# frozen_string_literal: true

ROM::SQL.migration do
  change do
  end
end

It's up to us to fill this out. Let's do so:

# frozen_string_literal: true

ROM::SQL.migration do
  change do
    create_table :users do
      primary_key :id
      column :first_name, String
      column :last_name, String
      column :age, Integer

      column :created_at, DateTime, null: false
      column :updated_at, DateTime, null: false
    end
  end
end

In this migration, we've specified six columns. We've had to specify the primary_key here, because ROM does not assume that all primary keys are id by default.

To run this migration, we will need to run:

rake db:migrate

If we see this:

... INFO -- : Finished applying migration [timestamp]_create_users.rb, direction: up, took [duration] seconds
<= db:migrate executed

Then the migration has been successfully applied.

Repositories

In order to get data into and out of database tables with ROM, we need to create something called a repository. A repository is a class that is used to define a clear API between your database and your application.

To create one of these, we'll create a new file inside a new directory structure at lib/bix/repos/user_repo.rb:

module Bix
  module Repos
    class UserRepo < ROM::Repository[:users]

    end
  end
end

To use this class (and others that we will create later on), we'll need to create a new file at system/boot/persistence.rb to setup our database configuration for our application:

Bix::Application.boot(:persistence) do |app|
  start do
    register('container', ROM.container(:sql, app['db.connection']))
  end
end

This file uses the rom gem to define a database configuration container and registers it with our application under the container key.

Next up, we'll create a new file over at bin/console with this in it:

#!/usr/bin/env ruby

require_relative '../config/application'

Bix::Application.finalize!

require 'irb'
IRB.start

This file will load our application's config/application.rb file. When this file is loaded, all the files in lib will be required. This includes our new lib/bix/repos/user_repo.rb file.

We call Bix::Application.finalize! here to start our application and all of its dependencies, this includes the two system-level dependencies specified in system/boot.

Once those classes are loaded and the application is finalized bin/console will start an IRB prompt.

To make it so that we can run bin/console, let's run this command:

chmod +x bin/console

We can now launch our console by running:

bin/console

When we're in this console, we can use our repository:

>> Bix::Repos::UserRepo.new(Bix::Application['container'])

This code will tell our user repository to connect to the database specified by the configuration contained within Bix::Application['container']. But unfortunately for us, another key part of configuration is missing and so we're going to see an error when we run this code:

ROM::ElementNotFoundError (:users doesn't exist in ROM::RelationRegistry registry)

For this code to work, we're going to need one extra class: a relation.

Relations

A relation class is used to represent data returning from a database, and is used most often by the repository itself. If we had a need for complex methods for working with data, they would go in "messy" relation methods, and then the repository would call those methods.

Here's an example from an app that I've worked on recently. I want to have a function that works on a notes table, counting up all the notes for a particular set of elements. In my relation, I have this code:

module Twist
  module Relations
    class Notes < ROM::Relation[:sql]
      schema(:notes, infer: true)

      def counts_for_element_ids(element_ids)
        where(element_id: element_ids)
        .select { [element_id, function(:count, :id).as(:count)] }
        .group(:element_id)
        .order(nil)
        .to_a
      end
    end
  end
end

The counts_for_elements method defines a query that will run against my database, and the final to_a on that query will return a dataset; an array of elements with their note counts.

However, this query will only return counts for elements that have counts, rather than all specified elements. In this particular application, I want a count for all elements specified in element_ids, regardless if they have notes or not. The place for this particular logic is in the repository:

module Twist
  module Repositories
    class NoteRepo < Twist::Repository[:notes]
      def count(element_ids)
        counts = notes.counts_for_elements(element_ids)

        missing = element_ids.select { |id| counts.none? { |c| c.element_id == id } }
        counts += missing.map { |m| NoteCount.new(element_id: m, count: 0) }
        counts.map { |element_id:, count:| [element_id, count] }.to_h
      end
    end
  end
end

The repository's code is all about working with the data. It does not know how to build the query for the data -- that responsibility is the relation's.

In short: relations run queries to get data out of a database, repositories define methods to work data returned by relations.

Back to Bix!

Let's define our relation now at lib/bix/relations/users.rb:

module Bix
  module Relations
    class Users < ROM::Relation[:sql]
      schema(:users, infer: true)
    end
  end
end

This relation class inherits from ROM::Relation[:sql], and that will meant hat our relation is used to talk to an SQL database.

Inside the class itself, there's a method called schema. This method says that our relation class is for a table called users and that we should infer the attributes for that schema -- meaning ROM will look at the table to define the attributes for this relation.

This almost gets us past the error we saw previously:

ROM::ElementNotFoundError (:users doesn't exist in ROM::RelationRegistry registry)

However, we will need to register relations with our application's database container. To do this, we can change system/boot/persistence.rb:

Bix::Application.boot(:persistence) do |app|
  start do
    config = app['db.config']
    config.auto_registration(app.root + "lib/bix")

    register('container', ROM.container(app['db.config']))
  end
end

This file will now automatically register this relation under lib/bix, and any other ROM things we add in later. This means that our User repository will be able to find the Users relation.

Let's run bin/console again and try working with our repository again:

>> user_repo = Bix::Repos::UserRepo.new(Bix::Application['container'])
>> user_repo.all
NoMethodError (undefined method `all' for #<Bix::Repos::User struct_namespace=ROM::Struct auto_struct=true>)

Oops! Repositores are intentionally bare-bones in ROM; they do not come with very many methods at all. Let's exit the console and then we'll define some methods on our repository. While we're here, we'll add a method for finding all the users, and one for creating users. Let's open lib/bix/repos/user_repo.rb and add these methods:

module Bix
  module Repos
    class UserRepo < ROM::Repository[:users]
      commands :create,
        use: :timestamps,
        plugins_options: {
          timestamps: {
            timestamps: %i(created_at updated_at)
          }
        }

      def all
        users.to_a
      end
    end
  end
end

The commands class method defines built-in commands that we can use on our repository. ROM comes with three: :create, :update and :delete.

This one tells ROM that we want a method called create that will let us create new records. The use :timestamps at the end tells ROM that we want create to set created_at and updated_at when our records are created.

The all method here calls the users relation, and the to_a will run a query to fetch all of the users.

With both of these things in place, let's now create and retrieve a user from the database through bin/console:

user_repo = Bix::Repos::UserRepo.new(Bix::Application['container'])
user_repo.create(first_name: "Ryan", last_name: "Bigg", age: 32)
=> #<ROM::Struct::User id=1 first_name="Ryan" last_name="Bigg" age=32 ...>

user_repo.all
=> [#<ROM::Struct::User id=1 first_name="Ryan" last_name="Bigg" age=32 ...>]

Hooray! We have now been able to add a record and retrieve it. We have now set up quite a few components for our application:

  • config/boot.rb - Requires boot-level pieces of our application -- such as Bundler and dotenv
  • config/application.rb - Defines a Container for our application's configuration
  • system/boot/db.rb - Specifies how our application connects to a database
  • system/boot/persistence.rb - Defines a ROM container that defines how the ROM pieces of our application connect to and interact with our database
  • lib/bix/relations/users.rb - Defines a class that can contain query logic for our users table
  • lib/bix/repos/user_repo.rb - A class that contains methods for interacting with our relation, allowing us to create + retrieve data from the databse.

ROM and Dry separate our application into small, clearly defined pieces with individual responsibilities. While this setup cost feels large now, it's a cost that we're only going to be paying once; Setup cost is one-time, maintenance cost is forever.

Entities

Now what happens if we want to add a custom method on to the objects returned by our database? Let's say, a full_name method that would let us combine a user's first_name and last_name attributes. Currently these are ROM::Struct::User objects, returned from ROM. There isn't a place to define these methods in our application yet. So let's create one!

To be able to define custom methods like full_name for users, we're going to need a class. For this, ROM has a feature called entities. These are simple classes that can be considered as super-powered structs. Let's build a new one by creating it in a new directory called lib/bix/entities, and calling it user.rb:

module Bix
  class User < ROM::Struct
    def full_name
      "#{first_name} #{last_name}"
    end
  end
end

Ignoring the falsehoods programmers believe about names, this method will combine a user's first_name and last_name attributes.

To use this class though, we need to configure the repository further over in lib/bix/repos/user_repo.rb:

module Bix
  module Repos
    class UserRepo < ROM::Repository[:users]
      struct_namespace Bix

      ...
    end
  end
end

This struct_namespace method tells the repository that when it builds structs, it can use the Bix namespace for those structs. The class name will be the singularised version of the relation specified in the ROM::Repository class inheritance: Bix::User.

Let's go back into bin/console and try this out:

user_repo = Bix::Repos::UserRepo.new(Bix::Application['container'])
user_repo.all.first.full_name
# => "Ryan Bigg"

Great! We're now able to have a class that contains custom Ruby logic for the data that is returned from the database.

Specifying the container automatically

When we initialize our repository, we have to use some really long code to do that:

user_repo = Bix::Repos::UserRepo.new(Bix::Application['container'])

What if we were able to do this instead?

user_repo = Bix::Repos::UserRepo.new

Wouldn't that be much nicer?

Well, with another one of the dry-rb set of gems, we can indeed do this. The last gem that we'll use in this part is one called dry-auto_inject. This gem will make it so that the dependency of the database container will be auto(matically) injected into the Bix::Repos::User class.

Let's get started with this gem by adding the dry-auto_inject gem into our Gemfile:

gem 'dry-auto_inject'

Then we'll run bundle install to install this gem.

Next up we'll add two lines to config/application.rb. The first one is to require this gem:

require "dry/auto_inject"

Next, we'll need to define a new constant in this file:

module Bix
  class Application < Dry::System::Container
    ...
  end

  Import = Dry::AutoInject(Application)
end

This Import constant will allow us to import (or inject) anything registered with our application into other parts. Let's see this in action now by adding this line to lib/repos/user_repo.rb:

module Bix
  module Repos
    class UserRepo < ROM::Repository[:users]
      include Import["container"]

      ...
    end
  end
end

This line will use the Import constant to inject the container dependency into this class. This works by passing in a container keyword argument to initialize for this class.

Let's try initializing a repository again in bin/console:

user_repo = Bix::Repos::UserRepo.new
# => #<Bix::Repos::User struct_namespace=Bix auto_struct=true>
user_repo.all.first.full_name
# => "Ryan Bigg"

Everything seems to be working correctly!

Summary

In this first part of the ROM + Dry showcase, we've seen how to setup a small application that can talk to a database.

We have created files that allow us to bootstrap our application's environment -- config/boot.rb and config/application.rb. Along with this, we have created system/boot, a directory that contains system-level dependencies for our application's boot process.

In the lib directory, we have setup three directories:

  • entities - Classes that represent specific data types returned from our database.
  • relations - Classes that can contain custom methods for querying the database
  • repos - Classes that provide a place for defining a public API between relations and our application code

This separation of concerns across our application will make it easier to work with in the long run. One more time: the setup cost is paid once, the maintenance cost is paid forever.

In the last part of this guide, we used the dry-auto_inject gem to inject the ROM container dependency into our Repos::User class. This will allow us to reduce the code that we need to write whenever we want to initialize the repository.

In the next part, we're going to look at how to use more dry-rb gems to add validations to our application, and we'll see another benefit of dry-auto_inject demonstrated.

Friday, 31. January 2020

Code With Jason

How to deploy a Ruby on Rails application to AWS Elastic Beanstalk

Overview

This tutorial will show you how to deploy a Rails application to AWS Elastic Beanstalk.

Using Elastic Beanstalk is just one of many (perhaps an infinite number of!) AWS deployment options. Each approach has different pros and cons. I’ll briefly go over some of them because it’s good to understand the pros and cons of various approaches (at least to an extent) before choosing one.

Manual EC2 deployment

One option is to do things “the old fashioned way” and manually set up a Rails application on a single EC2 instance. This is the approach I go over in this AWS/Rails deployment post and it’s perfectly fine for hobby projects where the stakes are low.

The downside to manual EC2 deployment is you end up with a snowflake server, a server with a one-of-a-kind configuration that’s hard to understand, modify, or replicate.

Elastic Beanstalk

Elastic Beanstalk is kind of analogous to Heroku. The basic idea is the same in that both Elastic Beanstalk and Heroku are abstraction layers on top of AWS services. The big difference is that Heroku is generally really easy and Elastic Beanstalk is a giant pain in the ass.

But the upside is that EB provides more easily replicable and understandable server instances than a manual EC2 instance. The server configuration is expressed in files, and then that configuration can be applied to an indefinite number of servers. This makes scaling easier. It’s also nice to know that if I somehow accidentally blow away one of my EC2 instances, EB will just automatically spin up a new identical one for me.

Another drawback to EB is that I understand EB can be kind of overly rigid. I ran into this trouble myself on a project where I needed to set up Sidekiq. I discovered that EB boxed me in in a way that made Sidekiq setup very difficult. So for a production project that grows over time, EB is perhaps a good place to start, but it should be expected that you might want to migrate to something more flexible sometime in the future.

An infrastructure-as-code approach

An infrastructure-as-code approach is probably the best long-term solution, although it currently also seems to be the most difficult and time-consuming to set up initially.

Options in this area include Ansible, Chef, Puppet, ECS, and probably a lot more. I’ve personally only used Ansible. I found Ansible to be great. This post will of course only cover Elastic Beanstalk though.

Configuration steps

Here are the steps we’ll be carrying out in this tutorial.

  1. Install the Elastic Beanstalk CLI
  2. Create the Elastic Beanstalk application
  3. Create the Elastic Beanstalk environment
  4. Create the RDS instance
  5. Deploy

Let’s get started.

Install the Elastic Beanstalk CLI

Much of what we’ll be doing involves the Elastic Beanstalk CLI (command-line interface). It can be installed with this command:

$ brew update && brew install awsebcli

Create the Elastic Beanstalk application

Now cd into the directory that contains your Rails project and run eb init.

$ eb init

When prompted, Select Create new Application. Accept the defaults for all other options.

When this command finishes running you’ll end up with a file called .elasticbeanstalk/config.yml that looks something like this:

branch-defaults:
  master:
    environment: null
    group_suffix: null
global:
  application_name: discuss_with
  branch: null
  default_ec2_keyname: aws-eb-cwj-post
  default_platform: Ruby 2.6 (Passenger Standalone)
  default_region: us-east-1
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: personal
  repository: null
  sc: git
  workspace_type: Application

Now that we’ve created our Elastic Beanstalk application, we’ll need to create an Elastic Beanstalk environment inside of that application. I typically set up one production environment and one staging environment inside a single application.

Create the Elastic Beanstalk environment

The command to create an environment is eb create.

$ eb create

You’ll be prompted for what to call your environment. I called mine discuss-with-production.

For load balancer type, choose application.

This step will take a long time. When it finishes, health will probably be “Severe”. Ignore this.

Set up SECRET_KEY_BASE

We’ll need to set a value for the SECRET_KEY_BASE environment variable. This can be done using the following eb setenv command which just sets the variable to a random string.

$ eb setenv SECRET_KEY_BASE=$(ruby -e "require 'securerandom';puts SecureRandom.hex(64)")

Set up ebextensions

With Elastic Beanstalk, you can add files in an .ebextensions directory at your project root to control how your server is configured. We need to add three ebextensions files.

The first, .ebextensions/01_ruby.config, looks like this:

packages:
  yum:
    git: []

container_commands:
  01_assets:
    command: RAILS_ENV=production bundle exec rake assets:precompile
    leader_only: true

The second, .ebextensions/02_yarn.config, looks like this:

commands:

  01_node_get:
    cwd: /tmp
    command: 'sudo curl --silent --location https://rpm.nodesource.com/setup_13.x | sudo bash -'

  02_node_install:
    cwd: /tmp
    command: 'sudo yum -y install nodejs'

  03_yarn_get:
    cwd: /tmp
    # don't run the command if yarn is already installed (file /usr/bin/yarn exists)
    test: '[ ! -f /usr/bin/yarn ] && echo "yarn not installed"'
    command: 'sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo'

  04_yarn_install:
    cwd: /tmp
    test: '[ ! -f /usr/bin/yarn ] && echo "yarn not installed"'
    command: 'sudo yum -y install yarn'

The last, .ebextensions/gem_install_bundler.config, looks like this:

files:
  # Runs before `./10_bundle_install.sh`:
  "/opt/elasticbeanstalk/hooks/appdeploy/pre/09_gem_install_bundler.sh" :
    mode: "000775"
    owner: root
    group: users
    content: |
      #!/usr/bin/env bash

      EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
      EB_SCRIPT_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k script_dir)
      # Source the application's Ruby
      . $EB_SCRIPT_DIR/use-app-ruby.sh

      cd $EB_APP_STAGING_DIR
      echo "Installing compatible bundler"
      gem install bundler -v 2.0.2

Deploy the application

Now we can make our first deployment attempt.

$ eb deploy

Unfortunately, it doesn’t work. We get an error that says: /opt/elasticbeanstalk/hooks/appdeploy/pre/12_db_migration.sh failed.

Why does this happen? Because he haven’t set up a database yet. Let’s do that now.

Create the RDS instance

In the AWS console, go to RDS, go to Databases, and click Create database.

Choose Postgresql and Free tier.

Choose whatever name you like for your database. I’m calling mine discuss-with-production

For size, choose t2.micro.

Make sure to set public accessibility to Yes so you can remotely connect to your database from your development machine.

Click Create database.

On the next screen, click View credential details.

Copy what’s there to a separate place for later use. You’ll also need the RDS instance’s endpoint URL which is found in a different place, under Connectivity & security and then Endpoint & port.

Make sure your database’s security group has port 5432 open.

Set the database credentials and create the database instance

Our production server will need to know the database endpoint URL and database credentials. Run the eb setenv command, with your own values of course replaced for mine, to set these values.

$ eb setenv RDS_DATABASE=discuss-with-production RDS_USERNAME=postgres RDS_PASSWORD=your-password RDS_HOST=your-endpoint-url

Even though the RDS instance exists, our actual PostgreSQL instance doesn’t exist yet. The RDS instance itself is more like just a container. We can run the rails db:create command remotely on the RDS instance by supplying the RDS endpoint URL when we run rails db:create.

Before running this command, make sure the production section of config/database.yml matches up with these environment variable names as follows:

production:
  <<: *default
  database: <%= ENV['RDS_DATABASE'] %>
  username: <%= ENV['RDS_USERNAME'] %>
  password: <%= ENV['RDS_PASSWORD'] %>
  host: <%= ENV['RDS_HOST'] %>
  port: 5432

Now create the database.

$ RDS_DATABASE=discuss-with-production RDS_USERNAME=postgres RDS_PASSWORD=your-password RDS_HOST=your-endpoint-url rails db:create

Deploy the application

Now the application can finally be deployed for real using eb deploy.

$ eb deploy

Once this finishes you can use the eb open command to visit your environment’s URL in the browser.

$ eb open

The post How to deploy a Ruby on Rails application to AWS Elastic Beanstalk appeared first on Code with Jason.

Wednesday, 29. January 2020

Mislav Marohnić @ Zagreb › Croatia

The past and the future of hub

I have been maintaining hub, the command-line git extension, for 10 years. After 2,100 issues and pull requests closed, 18k+ stars on GitHub, and countless hours invested in it, I thought it might be fitting to reflect on its unlikely past, share a bit about my process working on it, and address the future of GitHub on the command line.

In 2010, the entire implementation of hub 1.0 sat in a single Ruby file of less than 500 lines of code.

Hub was created as a pet project of Chris Wanstrath, the co-founder and then-CEO of GitHub. The initial idea behind hub was simple: use it to wrap git, and hub will expand arguments for you so you can type less while working with GitHub. For example, you can do git clone <owner>/<repo> instead of typing the full URL. In fact, expanding shorthand syntax to full URLs was most of what hub did back then—it didn’t even consult the GitHub API to perform any of its features.

I liked the idea of hub and I started contributing to the project early on. Chris’ own involvement has tapered off over the course of a year and, after a while, I was the only one who decided on hub’s features. In the long run, this might have not been great for the overall health of the project.

Since it is relatively easy to prototype new features in Ruby, I started expanding hub to wrap even more git commands, enabling it to do powerful things that literally nobody has asked for, such as cherry-picking commits from GitHub URLs. At the same time, together with other contributors I have also been adding brand new commands to hub such as create, fork, and pull-request. I did not recognize this at the time, but this went completely against the initial design of hub, which had mostly aimed to wrap existing git commands, and where the only “custom” hub command was browse.

Meanwhile, the original premise of hub being a wrapper for git was disappointing people who have tried it and concluded that it makes git a magnitude slower, sometimes even by more than 140ms. The slowness of hub has prompted Owen Ou (@jingweno) to create his own re-implementation of hub called “gh”, written entirely in an up-and-coming language called Go.

The much faster “gh” has hit a chord with the community. Coincidentally, a couple of GitHubbers were at the time paving the way for Go being used internally for GitHub microservices, and they pitched the idea that GitHub adopts the implementation of “gh” as the official “GitHub CLI”.

The way this unfolded at the time gave me mixed feelings. While I was also really impressed with Owen’s re-implementation, the idea shoving hub’s legacy to the side and promoting a relatively new project into something “official” didn’t sit well with me, primarily because I wasn’t initially included nor consulted in this planing, but also because I was worried about the incompatibilities between the implementations. So, I worked with Owen for over 6 months, teaching myself Go in the process, so that we could get to a point where the new implementation passed the entirety of hub’s test suite. Since I was nomadic at the time, at one point we even met up in Vancouver and sat down to hack on the project together. Making connections like these is what makes me happy about the world of open source.

In October 2014, Owen had the privilege to merge his hard work into the mainline and subsequently delete the entirety of the old Ruby implementation. (It turns out, git supports merging branches even if they have unrelated histories, so we were able to preserve complete histories of both projects in a single repository.) It’s the most epic rewrite that I have ever participated in, and I thank Owen for investing his patience and trust in me, and for making hub better for everyone.

We continued to call the project “hub” and never labeled it “the GitHub CLI”, though. This was because, by then, the limitations and the ensuing identity crisis of hub’s design was becoming apparent to me, and I couldn’t really endorse it as an official product in good faith. Hub continued to live on under the github org, but more as a sandbox where I continued to experiment with the possibilities of using the GitHub API on the command line.

And such a sandbox it was. Over the years, hub accumulated a portfolio of wild hacks that partly served a practical purpose, but that were mostly done to satisfy my thirst for experimentation. Some of these are:

I think that something that I did right with hub was that I never forgot to have fun while making it. This, combined with my keeping of healthy boundaries when dealing with the users’ requests, has significantly helped me stave off the onset of burnout.


It was clear to me, however, that I won’t be working on this project for the next 10 years as well.

Being my first Go project, hub is spectacularly messy, as it is evident from the existence of such constructs as the “github” package which encapsulates basically half of the entire codebase. Furthermore, as hub was getting more features in the form of new commands, it became to dawn on me that I’m really resisting upholding hub’s original premise of being a git wrapper, and so I stopped suggesting in the documentation that people do alias git=hub in their shells. In fact, I haven’t used it in the aliased form myself for several years already.

Expanding the git command with new features may sound like a fun gimmick, but is in fact surprisingly hard to maintain. Even though git lets you add new custom commands by adding git-<whatever> executables to your PATH, it’s not possible to override git core commands using that mechanism. To augment core commands you would need to create a new program that acts like git and convince people to alias your program as git. From that point onward, your program needs to behave as git in every possible way, and every time it doesn’t, you have a bug. Over the years, hub had more than plenty of these.

Let’s say that you want to implement a git clone <owner>/<repo> command and have it auto-expand the URL of a repository. Here are some considerations your program would have to make, right off the top of my head:

  • To isolate the <owner>/<repo> argument, you need to parse command-line flags exactly how core git clone does. Whenever you think you have reached parity, a new version of git that adds new flags may come out and you might be forced to compensate.
  • Core git clone also supports cloning local directories. If the <owner>/<repo> portion also happens to match a directory that happens to exist locally, should it expand to a URL or stay unchanged?
  • Before you can scan the filesystem to solve cases such as in the previous item, you need to first parse, respect, and forward to nested git invocations all global flags such as git -C <dir> --work-tree=<path> clone ....
  • When you expand the repo clone URL, should you use the https:, git:, ssh: or other protocols? How do you make the right decision as default, and how to you let the user choose their preference?
  • What if the user doesn’t intend to clone this repo from github.com, but from their GitHub Enterprise instance on another host? You now need to support selecting the hostname and maintaining different modes of Enterprise authentication.
  • If you want to support SSH clone URLs, you now also need to parse and respect hostname aliases from the user’s ~/.ssh/config file.
  • When you expand a git command with new functionality/flags, how are you going to add that information to git clone -h? Remember that there are also man git-clone, and git help clone [--web].
  • When you add new flags to a git command, how are you going to make sure that the additions appear in git completions for bash, zsh, fish, and possibly other shells?

In hub, we’ve made decisions and workarounds for every of the above points and many more, but they always fell short. There was always something that we missed; some edge case that we haven’t considered. For example, the brittlest of all hub features are its extensions to core git completions that inject extra commands and flags into different shells. This never worked perfectly in the first place, kept falling out of date, and frequently breaks with newer releases of git. In the end, maintaining something like this is a Sisyphean task.

Instead on focusing on git extensions, over the course of the last couple of years I gently steered the direction of the project to act more as a command-line API client with a focus on functionality that facilitates scripting. By shipping such features I was able to close dozens of feature requests for hub with an explanation that users are now able to script their workflows without hub necessarily implementing them. It worked wonderfully.


If I was redesigning hub today, I would make an entirely different set of decisions.

First of all, I wouldn’t even consider making a git proxy anymore. I love git, but my time is better spent doing things other than carefully reimplementing parts of core git functionality. Git already has a plethora of functionality and instead of extending it, I now understand that the way to improve git is to design better abstractions around it. Of course, the latter is much harder work, since every abstraction will inevitably fail to encapsulate someone’s particular flow. This effect could potentially be mitigated by better defining and understanding who the audience of your product is.

Second, I would focus on strictly maintaining a command-line scripting core that does little more than offer GitHub API authentication, encoding, and logic that maps git remotes to GitHub repositories. All auxiliary features—such as custom commands—would be built on top of this core. Furthermore, anyone could roll their own commands; users wouldn’t need rely on the mainline to cover their use-cases as much and there would be less technical debt over time.

Third, instead of feeding my own personal Not Invented Here syndrome, I would opt to use more community-supported libraries and tools to avoid maintaining too many custom approaches of my own. Every component that an open source project implements in an unusual way is a potential barrier to contribution, and I have a feeling that hub is difficult to contribute to since many people offer to make a fix or implement a feature, but very few actually follow up with a pull request.

Luckily, I am given a chance to make an entirely different set of decisions: for the first time in 10 years, GitHub is investing in having an official “GitHub CLI” product of their own and they hired me to work on the project as my day job. My new team is largely people who make the awesome GitHub Desktop and together we sat down and made a decision early on to start a new product from scratch rather than building on the rickety foundation of the hub codebase.

The GitHub CLI that we are building is not exactly what I would have chosen to create if I was the only person in charge of making it, but this is A Good Thing. Before, I never really made an effort to understand who the audience of hub was, but with an actual team we finally get to explore that and hopefully build something that’s ambitious not in terms of the number of features it offers or how much of the GitHub API it covers, but in how well it helps people be productive with their daily work.

What does this mean for the future of hub? Since I personally don’t find it valuable to spend my time maintaining two separate command-line clients for GitHub, I will gradually reduce my involvement with hub to a point where it either goes into complete feature-freeze mode or finds new maintainership. It’s still too early for me to tell how exactly any of this is going to play out, but rest assured that hub is going to continue to exist and receive bug fixes until further notice. I still use hub every day and I have no intention of disappointing any people who do the same.

If you have any further questions or ideas about GitHub features that you would you like to see on the command line, please reach out to me. Thank you for reading! 🙇‍♂️


Fast Ruby Blog

Why Is It Important to Upgrade Your Rails Application?

Technology evolves quickly. When you first created your Rails application, it's likely you did it using the latest version of Rails. As time passes, however, new Rails versions come out and your application becomes more and more out of touch with the latest version. But why is this important? Why does it matter?

There are several different reasons to upgrade your Rails application. In this article, I’ll list what we consider to be the most important ones.

Security

Security is a big issue. You want your application to be as safe as possible and, to accomplish that, one of the pivotal things to do is to make sure Ruby on Rails is up to date. Ruby on Rails is the application framework underlying everything your web application does.

As new threats arise, new versions contain security patches to protect you against them. Being up to date on your Rails version allows you to rapidly apply the newest security patches to your application.

For example, if your application is on Rails 4.2, then you're probably exposed to the vulnerabilities described here.

Additionally, a great service you can use is Dependabot. It will help you keep your dependencies secure and up-to-date.

Improved Performance

This is another big point. Users want to be able to do things and they want to be able to do it fast. Websites and applications are getting bigger and bigger. This creates a big demand for speed.

With almost every new Rails version comes new performance upgrades. Upgrading your Rails application ensures it is up to date with the newest performance upgrades.

Stability

Of course you want your application to be stable. But your app's stability takes a hit when new bugs are discovered. And in the same way you don’t want to leave bugs discovered within your application unaddressed, you also don’t want bugs discovered in your application’s framework unaddressed. Whenever a new bug is discovered by the Rails community or by the creators, it’ll usually be fixed by the next release.

Upgrading your Rails application ensures you apply those bug fixes to your app.

Improvements to the Ruby Language

Rails is a framework. Ruby is the underlying programming language. You can think of it as Ruby being the bricks and Rails being prefabricated houses. You need the bricks to build the houses. So you need bricks that will allow your prefabricated houses to be built.

It is the same with programming languages and frameworks. You want to take advantage of the evolution of the programming language as it gets more powerful and flexible. Upgrading your Rails application ensures you can also take advantage of the latest improvements to the Ruby language.

New Gems and Ecosystem Updates

Rails depends on gems and libraries. These gems and libraries are also evolving and improving as Rails evolves and improves.

Since Rails depends on these gems and libraries (which allow developers to solve fundamental problems faster), keeping up to date with your Rails version allows you to take advantage of the improvements to these gems and libraries improvements, as well as taking advantage of new gems.

Conclusion

There are many different reasons to upgrade your Rails application. The 5 listed above are what we consider to be the most important and should already tell you staying up to date with the latest version of Rails is beneficial to your app.

If you need some guidance on upgrading your Rails application check out our free eBook: The Complete Guide to Upgrade Rails

Tuesday, 28. January 2020

OneBitCode

NÃO seja esses dois tipos de Programadores!

 

E aiiii Programador(a), tudo bem?

Existem dois tipos de programadores que você deve correr o mais rápido possível quando eles estiverem por perto! 😂
Eu fiz um vídeo no formato de vlog falando sobre isso, deixa um feedback lá no vídeo ou aqui no blog me contando se curtiu o modelo e o conteúdo.

Valeuuuuuuuuuuuuu! ❤

O post NÃO seja esses dois tipos de Programadores! apareceu primeiro em OneBitCode.


Ruby Tuesday

Issue #19

  • Remember Sorbet? It gained a lot of attention last year, but was kind of forgotten quickly. Especially now that built-in type ckecking for Ruby is promised in 3.0, its future is unknown. However, if you’d like to know more about the meat, read an article explaining why Sorbet typechecking is fast. It’s a good technical read.
  • For those who like very technical articles, here’s a piece about Ruby’s JIT and how it can be better/different. It’s called MIR: A lightweight JIT compiler project and contains quite a lot of knowledge about language design and implementation.
  • Another one in language implementation department is about Evaluating Ruby in Ruby by Ilya Bylich.
  • Machine learning and related topics are dominated by Python, C++ and Java. But it does not mean that we can’t have it in Ruby too! Andrew Kane spent a lot 5 months making 16 ML tools available for Ruby. They are usually just wrappers on C/C++ libraries, but the possibility to use them right from your ruby code is pretty cool. Read more here.
  • Here’s one from 2010, but still very interesting: Maze Generation: Eller’s Algorithm. You’d probably hate me but… it’s about generating mazes using Eller’s algorithm ¯_(ツ)_/¯ And the code is in Ruby, so it should be easier to wrap your head around it (it was for me).
  • And a news from today: if you care about code coverage, you have probably heard about SimpleCov. With its latest realease it adds branch coverage. So you will no longer easily get away with false ? 1 : 0-like code.

Monday, 27. January 2020

RubyMine Blog

RubyMine 2020.1 EAP is Open!

Hello everyone,

Today we are happy to announce the opening of the Early Access Program (EAP) for RubyMine 2020.1! You can get EAP builds from our website, use the Toolbox App, or update to them using snaps if you’re an Ubuntu user. Note that the EAP builds are free to use but expire within 30 days of the build date.

In the first EAP build, you’ll find new options for working with Docker Compose SDKs, support for running Rake tasks from the gutter area, an improved experience for creating tests, and some other nice features. We’ve also fixed up several annoying little issues related to the debugger and to Docker support. Let’s take a look at the most interesting improvements first.

Creating tests

With this EAP, we’ve improved the process of creating new tests when navigating from a test subject. RubyMine can now create a new test for any class, replicating the directory structure based on the path to the test subject. For example, our Rails application has two test folders: the test folder for Minitest tests and the spec folder without tests for RSpec. If you open the app/models/user.rb file containing a User model and select Navigate | Go to Test (⇧⌘T / Ctrl+Shift+T) from the main menu, RubyMine will display the following popup.
Choose Test popup
Select Create New Test here. In the invoked dialog, RubyMine offers you the choice of whether to create a new test in a new spec/models folder that does not yet exist or add a test in test/models.
Choose Destination Directory dialog
After choosing spec/models and clicking OK, you only need to provide the class name and choose whether to create a test from scratch or use a predefined test template.
New Test popup
Press Enter to finish, and that’s it!

Running Rake tasks from the gutter

RubyMine already provides a quick and easy way to run Rake tasks using the Run Anything popup. Starting with 2020.1, you can also run, debug, and profile your tasks right from the gutter menu.
Run Rake tasks from gutter
As always, RubyMine automatically creates corresponding run/debug configurations for the tasks that were run.
Run/debug configurations for Rake tasks

Smart execution of terminal commands

RubyMine provides a nice coherent user experience for executing various actions. For example, when you run tests, you can quickly specify the required environment in the dedicated dialog, examine test results in the Run tool window, and then reuse the automatically created test configuration for running tests again. With v2020.1, you can take advantage of these features when executing commands from the embedded RubyMine terminal emulator. Once an input command is matched, the terminal highlights it.
Match terminal command
You can run this command by pressing Ctrl+Enter. The IDE will show a special UI for running this command.
Execute test dialog

To try this feature out, enable the terminal.shell.command.handling flag in the Experimental Features dialog.
Tip: To invoke Experimental Features, press Ctrl+Shift+A / ⇧⌘A and start typing ‘Experimental Features’.

Docker Compose improvements

With RubyMine, you can run, debug, and test your application in a particular environment by using Docker Compose as a remote interpreter. To work with the Ruby interpreter inside Docker, the IDE analyzes the Ruby environment by running specific commands inside a container, such as which ruby, gem env, and rbconfig. Then, RubyMine copies the Ruby SDK and gems to a local cache to enable the use of code insight features, such as code completion or navigation.
With v2020.1, you can adjust how the IDE interacts with Docker to analyze the Ruby environment. Open the Settings/Preferences dialog and go to the Ruby Docker Integration page.
Ruby Docker Integration settings

The Interaction mode allows you to specify how the IDE runs commands for analyzing the Ruby environment. For example, you can select whether to execute these commands in a target container (docker-compose exec), create a new environment (docker-compose run), or use the dedicated IDE auxiliary container for this purpose.

Machine-learning completion for Ruby

In v2019.3, we’ve introduced machine-learning (ML) completion for Ruby. This improves the code completion feature by reordering elements in the completion popup and ranking the more relevant items higher. In the image below, you can see the position changes for members exposed by the User model class with the enabled ML-completion: for example, the find_by method was moved up one position compared to the default completion mechanism, while find_by_sql dropped four positions.

Machine-learning completion

You can enable the ML-completion and change its settings on the Editor | General | Code Completion page of the Settings/Preferences dialog.
Machine-learning completion settings
Note that work on this feature is still in progress and we’re constantly working to improve the quality of ML-completion.

Important bug-fixes and other useful features

In addition to the features mentioned above, we’ve implemented the following fixes:

  • We fixed an issue that caused attaching to a process to kill it. RUBY-22070
  • The interactive console is available now when debugging under the Docker Compose SDK. RUBY-21611
  • We fixed the issue related to starting the debugger with jruby-9.2.9.0. RUBY-25336
  • A code coverage report is now shown when using the Ruby interpreter managed by chruby. RUBY-23732

Other notable changes include the following:

  • RubyMine now enables you to install the Bundler whose version differs from the one installed in the SDK. RUBY-25520
  • You can now search for usages of a parent method. RUBY-24658
  • Navigate | Go to Super Method now allows you to select the exact super method you want to navigate. RUBY-25554
  • In the Structure view, you can filter out the variables to look only at methods. RUBY-11850
  • We’ve added support for Module#module_function. RUBY-15259

Platform-wide changes

In addition to the specific improvements for Ruby and Rails, the following changes have been made for RubyMine and other IntelliJ-based IDEs:

  • With the v2020.1 EAP, we are fully moving to JetBrains Runtime 11 (JBR11), and will no longer distribute builds with JetBrains Runtime 8 (JBR8). Please note that all the IntelliJ IDEA 2020.1 updates, both in the IDE and the Toolbox App, will come with JBR 11.
  • RubyMine and other IntelliJ-based IDEs now use the brand-new JetBrains Mono font (Settings/Preferences | Editor | Font).
  • The IDEs now use the reworked Interactively Rebase from Here dialog that allows you to edit, combine, and remove your previous commits.
  • The light theme is now unified for all operating systems and goes by the name IntelliJ Light.
  • The Quick documentation popup appears on mouseover by default. You can disable this option in Settings/Preferences | Editor | General.

Please keep in mind that v2020.1 is still under heavy development and, therefore, the EAP builds might be unstable. Let us know about any issues you come across here in the comments, in our issue tracker, or on Twitter.

Happy Developing!
Your RubyMine team


Ruby Conferences 'n' Camps in 2020 - What's Upcoming?

Ruby Retreat New Zealand (NZ) @ Mt Cheeseman (near Christchurch), New Zealand Announced

Conferences 'n' Camps

What's News? What's Upcoming in 2020?

Ruby Retreat New Zealand (NZ)
Mar/27-30 (4d) Fri-Mon @ Mt Cheeseman (near Christchurch), New Zealand • (Updates)

See all Conferences 'n' Camps in 2020».


Notes By Strzibny

Using Elixir Language Server in Sublime Text 3

Elixir LS can bring us some IDE features to our editor of choice thanks to the Language Server Protocol (LSP). If you are like me, you prefer the speed and simplicity of Sublime Text. Let’s see how to compile Elixir Language Server and how to use it within Sublime Text 3 editor via LSP plugin on Fedora.

Before we dive in, let’s make sure that we have already installed both Sublime Text 3 and Elixir programming language. If you are on Fedora, one easy way to get ST3 running is to use Fedy which you can also use to install other cool software and utilities. For Elixir itself, the official elixir package is all that’s needed for Fedora systems.

Next we need to prepare and compile the Elixir Language Server (Elixir LS). To make sure it will work smoothly with our system we compile it from scratch. The process is no different to compiling other Mix projects:

$ cd elixir-ls
$ mix deps.get
$ mix compile
$ mix elixir_ls.release -o release</code></pre>

The last line will make a release for us to a directory of our choice. We need to remember this path for the next step. Specifically we need to locate a file called language_server.sh. It’s a good idea to try to run it so that it’s clear this step was successful.

Once the Elixir LS is in place we can continue and install two new packages in Sublime Text using Package Control. “Elixir” package for syntax highlighting and “LSP” for running language servers. To do so we simply invoke Package Control with CTRL+SHIFT+P shortcut and start typing “Install Package” followed by ENTER and the name of the package.

After that we need to tell the LSP package about our Elixir language server location so we hit CTRL+SHIFT+P again and search for “LSP Settings”. That should open two files, ~/.config/sublime-text-3/Packages/User/LSP.sublime-settings and .config/sublime-text-3/Packages/User/LSP/LSP.sublime-settings. We will edit the user file (which overrides the global settings) with the following (change the command path to the actual path!):

{
  "default_clients": {
    "elixir-ls": {
      "command": ["/path/to/release/language_server.sh"],
      "enabled": true,
      "languageId": "elixir",
      "scopes": ["source.elixir"],
      "syntaxes": ["Packages/Elixir/Syntaxes/Elixir.tmLanguage"]
    }
  }
}

The “default_clients” part is important as we have to use the same structure as in the global file. After saving the file we can go to any Elixir project and enable LSP using Package Control again by hitting CTRL+SHIFT+P and typing “LSP: Enable Language Server in Project”.

We should now get a report of warnings and failures on every save. Clicking any line will bring us directly to the location of the issue. That’s it!

Friday, 24. January 2020

Rails Girls Summer of Code Blog

Crowdfunding for 2020 scholarships has commenced

There is already so much buzz around the program this year. With student teams coming together, coaches signing up to help, and mentors submitting projects it gets more exciting by the day.

Now our focus turns to the scholarships, and we need your help.

Your chance to make a difference

Ten teams. That’s our aim. Provide ten teams - that’s 20 students - a chance to work on open source projects this year. But the cold reality is this requires funding.

By donating, you contribute to the scholarships that enable someone from an underrepresented group to get started in open source. Just take a look at what we’ve achieved so far since 2013:

RGSoC Stats 2013-18

Thanks to our early bird sponsors, we’re well on our way again in 2020. But we can do better. Aim high, but dream BIG. Let’s shatter even our own expectations and see how many teams we can fund this year.

#donatebecause

Visit our donation page and pledge whatever you can. Large or small amounts, it’s all important. It all helps bring diversity to tech and it all plays a part in training the women and non-binary individuals who are passionate about coding.

Over the next few months we’ll be sharing some personal stories in a new series of Alumni Interviews. These first-hand accounts are in former students’ own words and shine a light on how big an impact our humble program makes 😊

We also want to hear your reasons for supporting RGSoC 2020. Use the hashtag #donatebecause and you could be the reason someone joins our incredible community.

Thank you

We can’t say it enough. For what you make possible, thank you.

Thursday, 23. January 2020

Shorts

Ruby ML for Python Coders

Python and Ruby

Curious to try machine learning in Ruby? Here’s a short cheatsheet for Python coders.

Data structure basics

Libraries

Category Python Ruby
Multi-dimensional arrays NumPy Numo
Data frames Pandas Daru
Visualization Matplotlib Nyaplot
Predictive modeling Scikit-learn Rumale
Gradient boosting XGBoost, LightGBM XGBoost, LightGBM
Deep learning PyTorch, TensorFlow Torch-rb, TensorFlow (TensorFlow :construction:)
Recommendations Surprise, Implicit Disco
Approximate nearest neighbors NGT, Annoy NGT, Hanny
Factorization machines xLearn xLearn
Natural language processing spaCy, NTLK Many gems (nothing comprehensive :cry:)
Text classification fastText fastText
Forecasting Prophet :cry:
Optimization CVXPY, PuLP, SCS, OSQP CBC, SCS, OSQP
Reinforcement learning Vowpal Wabbit Vowpal Wabbit
Scoring engine ONNX Runtime ONNX Runtime, Menoh

This list is by no means comprehensive. Some Ruby libraries are ones I created, as mentioned here.

If you’re planning to add Ruby support to your ML library:

Category Python Ruby
FFI (native) ctypes Fiddle
FFI (library) cffi FFI
C++ extensions pybind11 Rice
Compile to C Cython Rubex

Give Ruby a shot for your next maching learning project!

Wednesday, 22. January 2020

Shorts

16 New ML Gems for Ruby

New ML Gems

In August, I set out to improve the machine learning ecosystem for Ruby. I wasn’t sure where it would go. Over the next 5 months, I ended up releasing 16 libraries and learned a lot along the way. I wanted to share some of that knowledge and introduce some of the libraries you can now use in Ruby.

The Theme

There are many great machine libraries for Python, so a natural place to start was to see what it’d take to bring them to Ruby. It turned out to be a lot less work than expected based on a common theme.

ML libraries want to be fast. This means less time waiting and more time iterating. However, interpreted languages like Python and Ruby aren’t relatively fast. How do libraries overcome this?

The key is they do most of the work in a compiled language - typically C++ - and have wrappers for other languages like Python.

This was really great news. The same approach and code could be used for Ruby.

The Patterns

Ruby has a number of ways to call C and C++ code.

Native extensions are one method. They’re written in C or C++ and use Ruby’s C API. You may have noticed gems with native extensions taking longer to install, as they need to compile.

void Init_stats()
{
    VALUE mStats = rb_define_module("Stats");
    rb_define_module_function(mStats, "mean", mean, 2);
}

A more general way for one language to call another is a foreign function interface, or FFI. It requires a C API (due to C++ name mangling), which many machine learning libraries had. An advantage of FFI is you can define the interface in the host language - in our case, Ruby.

Ruby supports FFI with Fiddle. It was added in Ruby 1.9, but appears to be “the Ruby standard library’s best kept secret.”

module Stats
  extend Fiddle::Importer
  dlload "libstats.so"
  extern "double mean(int a, int b)"
end

There’s also the FFI gem, which provides higher-level functionality and overcomes some limitations of Fiddle (like the ability to pass structs by value).

module Stats
  extend FFI::Library
  ffi_lib "stats"
  attach_function :mean, [:int, :int], :double
end

For libraries without a C API, Rice provides a really nice way to bind C++ code (similar to Python’s pybind11).

void Init_stats()
{
    Module mStats = define_module("Stats");
    mStats.define_singleton_method("mean", &mean);
}

Another approach is SWIG (Simplified Wrapper and Interface Generator). You create an interface file and then run SWIG to generate the bindings. Gusto has a good tutorial on this.

%module stats

double mean(int, int);

There’s also Rubex, which lets you write Ruby-like code that compiles to C (similar to Python’s Cython). It also provides the ability to interface with C libraries.

lib "<stats.h>"
  double mean(int, int)
end

None of the approaches above are specific to machine learning, so you can use them with any C or C++ library.

The Libraries

Libraries were chosen based on popularity and performance. Many have a similar interface to their Python counterpart to make it easy to follow existing tutorials. Libraries are broken down into categories below with brief descriptions.

Gradient Boosting

XGBoost and LightGBM are gradient boosting libraries. Gradient boosting is a powerful technique for building predictive models that fits many small decision trees that together make robust predictions, even with outliers and missing values. Gradient boosting performs well on tabular data.

Deep Learning

Torch-rb and TensorFlow are deep learning libraries. Torch-rb is built on LibTorch, the library that powers PyTorch. Deep learning has been very successful in areas like image recognition and natural language processing.

Recommendations

Disco is a recommendation library. It looks at ratings or actions from users to predict other items they might like, known as collaborative filtering. Matrix factorization is a common way to accomplish this.

LIBMF is a high-performance matrix factorization library.

Collaborative filtering can also find similar users and items. If you have a large number of users or items, an approximate nearest neighbor algorithm can speed up the search. Spotify does this for music recommendations.

NGT is an approximate nearest neighbor library that performs extremely well on benchmarks (in Python/C++).

ANN Benchmarks

Image from ANN Benchmarks, MIT license

Another promising technique for recommendations is factorization machines. The traditional approach to collaborative filtering builds a model exclusively from past ratings or actions. However, you may have additional side information about users or items. Factorization machines can incorporate this data. They can also perform classification and regression.

xLearn is a high-performance library for factorization machines.

Optimization

Optimization finds the best solution to a problem out of many possible solutions. Scheduling and vehicle routing are two common tasks. Optimization problems have an objective function to minimize (or maximize) and a set of constraints.

Linear programming is an approach you can use when the objective function and constraints are linear. Here’s a really good introductory series if you want to learn more.

SCS is a library that can solve many types of optimization problems.

OSQP is another that’s specifically designed for quadratic problems.

Text Classification

fastText is a text classification and word representation library. It can label documents with one or more categories, which is useful for content tagging, spam filtering, and language detection. It can also compute word vectors, which can be compared to find similar words and analogies.

Interoperability

It’s nice when languages play nicely together.

ONNX Runtime is a scoring engine for ML models. You can build a model in one language, save it in the ONNX format, and run it in another. Here’s an example.

Npy is a library for saving and loading NumPy npy and npz files. It uses Numo for multi-dimensional arrays.

Others

Vowpal Wabbit specializes in online learning. It’s great for reinforcement learning as well as supervised learning where you want to train a model incrementally instead of all at once. This is nice when you have a lot of data.

ThunderSVM is an SVM library that runs in parallel on either CPUs or GPUs.

GSLR is a linear regression library powered by GSL that supports both ordinary least squares and ridge regression. It can be used alone or to improve the performance of Eps.

Shout-out

I wanted to also give a shout-out to another library that entered the scene in 2019.

Rumale is a machine learning library that supports many, many algorithms, similar to Python’s Scikit-learn. Thanks @yoshoku for the amazing work!

Final Word

There are now many state-of-the-art machine learning libraries available for Ruby. If you’re a Ruby engineering who’s interested in machine learning, now’s a good time to try it. Also, if you come across a C or C++ library you want to use in Ruby, you’ve seen a few ways to do it. Let’s make Ruby a great language for machine learning.


Lucas Caton

Vale a pena aprender Ruby on Rails em 2020?

Esta é uma das perguntas que mais recebo. Para criar um contexto e responder da melhor forma, gravei e publiquei um vídeo no meu canal do YouTube:

Se curtir, não deixe de se inscrever no canal! 😉


David Copeland @ Washington, DC › United States

The Law of Demeter Creates More Problems Than It Solves

Most developers, when invoking the “Law of Demeter” or when pointing out a “Demeter Violation”, do so when a line of code has more than one dot: person.address.country.code. Like the near-pointless SOLID Principles, Demeter, too, suffers from making a vague claim that drives developers to myopically unhelpful behavior.

Writing SOLID is not Solid, I found the backstory and history of the principles really interesting. They were far flimsier than I had expected, and much more vague in their prescription. The problem was in their couching as “principles” and the overcomplex code that resulted from their oversimplification. Demeter is no different. It aims to help us manage coupling between classes, but when blindly applied to core classes and data structures, it leads to convoluted, over-de-coupled code that obscures behavior.

What is this Law of Demeter?

Update Jan 24, 2020: My former collegue Glenn Vanderburg pointed me to what he believes it he source of the “Law of Demeter”, which looks like a fax of the IEEE Software magazine in which it appears! It’s on the Universitatea Politehnica Timisoara’s website.

It does specifically mention object-oriented programming, and it states a few interesting things. First, it mentions pretty explicitly that they have no actual proof this law does what it says it does (maybe then don’t call it law? I dunno. That’s just me). Second, it presents a much more elaborate and nuanced definition than the paper linked below. The definitions of terms alone is almost a page long and somewhat dense.

Suffice it to say, I stand even more firm that this should not be called a “Law” and that the way most programmers understand by counting dots is absolutely wrong. This paper is hard to find and pretty hard to read (both due to its text, but also its presentation). I would be surprised if anyone invoking Demeter in a code review has read and understood it.

It’s hard to find a real source for the Law of Demeter, but the closest I could find is this page on Northeastern’s webstie, which says:

End of Update

This page on Northeastern’s webstie, summarizes the Law as stated in the paper above:

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.

The page then attempts to define “closely related”, which I will attempt to restate without the academic legalese:

  • A unit is some method meth of a class Clazz
  • Closely related units are classes that are:
    • other methods of Clazz.
    • passed into meth as arguments.
    • returned by other methods of Clazz.
    • any instance variables of Clazz.

Anything else should not be used by meth. So for example, if meth takes an argument arg, it’s OK to call a method other_meth on arg (arg.other_meth), but it’s not OK to call a method on that (arg.other_meth.yet_another_meth).

It’s also worth pointing out that this “Law” was not developed for the sake of object-oriented programming, but for help defining aspect-oriented programming, which you only tend to hear about in Java-land, and even then, not all that much.

That all said, this advice seems reasonable, but it does not really allow for nuance. Yes, we want to reduce coupling, but doing so has a cost (this is discussed at length in the book). In particular, it might be preferable for our code’s coupling to match that of the domain.

It also might be OK to be overly coupled to our language’s standard library or to the framework components of whatever framework we are using, since that coupling mimics the decision to be coupled to a language or framework.

Code Coupling can Mirror Domain Coupling

Consider this object model, where a person has an address, which has a country, which has a code.

Class diagram of our object model
Class diagram of the object model.

Suppose we have to write a method to figure out taxes based on country code of a person. Our method, determine_tax_method takes a Person as an argument. The basic logic is:

  • If a person is in the US and a contractor, we don’t do tax determination.
  • If they are in the US and not a contractor, we use the US-based tax determination, which requires a zipcode.
  • If they are in the UK, we use the UK based determination, which requires a postcode.
  • Otherwise, we don’t do tax determination.

Here’s what that might look like:

class TaxDetermination
  def determine_tax_method(person)
    case person.address.country.code
    when "US"
      if person.contractor?
        NoTaxDetermination.new
      else
        USTaxDetermination.new(person.address.postcode)
      end
    when "UK"
      UKTaxDetermination.new(person.address.postcode)
    else
      NoTaxDetermination.new
    end
  end
end

If address, country, and code are all methods, according to the Law of Demeter, we have created a violation, because we are depending on the class of an object returned by a method called on an argument. In this case, the return value of person.address is a Country and thus not a “closely related unit”.

But is that really true?

Person has a well-defined type. It is defined as having an address, which is an Address, another well-defined type. That has a country, well-defined in the Country class, which has a code attribute that returns a string. These aren’t objects to which we are sending messages, at least not semantically. These are data structures we are navigating to access data from our domain model. The difference is meaningful!

Even still, it’s hard to quantify the problems with a piece of code. The best way to evaluate a technique is to compare code that uses it to code that does not. So, let’s change our code so it doesn’t violate the Law of Demeter.

A common way to do this is to provide proxy methods on an allowed class to do the navigation for us:

class TaxDetermination
  def determine_tax_method(person)
    case person.country_code
    #           ^^^^^^^^^^^^           
    when "US"
      if person.contractor?
        NoTaxDetermination.new
      else
       USTaxDetermination.new(person.postcode)
       #                             ^^^^^^^^
      end
    when "UK"
     UKTaxDetermination.new(person.postcode)
     #                             ^^^^^^^^
    else
      NoTaxDetermination.new
    end
  end
end

How do we implement country_code and postcode?

class Person
  def country_code
    self.address.country.code
  end

  def postcode
    self.address.postcode
  end
end

Of course, country_code now contains a Demeter Violation, because it calls a method on the return type of a closely related unit. Remember, self.address is allowed, and calling methods on self.address is allowed, but that’s it. Calling code on country is the violation. So…another proxy method.

class Person
  def country_code
    self.address.country_code
    #            ^^^^^^^^^^^^
  end
end


class Address
  def country_code
    self.country.code
  end
end

And now we comply with the Law of Demeter, but what have we actually accomplished? All of the methods we’ve been dealing with are really just attributes returning unfettered access to public members of a data structure.

We’ve added three new public API methods to two classes, all of which require tests, which means we’ve incurred both an opportunity cost in making them and a carrying cost in their continued existence.

We also now have two was to get a person’s country code, two ways to get their post code, and two was to get the country code of an address. It’s hard to see this as a benefit.

For classes that are really just data structures, especially when they are core domain concepts that drive the reason for our app’s existence, applying the Law of Demeter does more harm than good. And when you consider that most developers who apply it don’t read the backstory and simply count dots in lines of code, you end up with myopically overcomplex code with little demonstrable benefit.

But let’s take this one step further, shall we?

Violating Demeter by Depending on the Standard Library

Suppose we want to send physical mail to a person, but our carrier is a horrible legacy US-centric one that requires being given a first name and last name. We only collected full name, so we fake it out by looking for a space in the name. Anyone with no spaces in their names is handled manually by queuing their record to a customer service person via handle_manually.

class MailSending
  def send_mailer(person)
    fake_first_last = /^(?<first>\S+)\s(?<last>.*)$/

    match_data = fake_first_last.match(person.name)

    if match_data
      legacy_carrier(match_data[:first], match_data[:last])
    else
      handle_manually(person)
    end
  end
end

This has a Demeter violation. A Regexp (created by the /../ literal) returns a MatchData if there is match. We can’t call methods on an object returned by one of our closely related units’ methods. We can call match on a Regexp, but we can’t call a method on what that returns. In this case, we’re calling [] on the returned MatchData. How do we eliminate this egregious problem?

We can’t make proxy methods for first name and last name in Person, because that method will have the same problem as this one (it also would put use-case specific methods on a core class, but that’s another problem). We really do need to both match a regexp and examine its results. But the Law does not allow for such subtly! We could create a proxy class for this parsing.

class LegacyFirstLastParser
  FAKE_FIRST_LAST = /^(?<first>\S+)\s(?<last>.*)$/
  def initialize(name)
    @match_data = name.match(FAKE_FIRST_LAST)
  end

  def can_guess_first_last?
    !@match_data.nil?
  end

  def first
    @match_data[:first]
  end

  def last
    @match_data[:last]
  end
end

Now, we can use this class:

class MailSending
  def send_mailer(person)
    parser = LegacyFirstLastParser.new(person.name)
    if parser.can_guess_first_last?
      legacy_carrier(parser.first, parser.last)
    else
      handle_manually(person)
    end
  end
end

Hmm. LegacyFirstLastParser was just plucked out of the ether. It definitely is not a closely-related unit based on our definition. We’ll need to create that via some sort of private method:

class MailSending
  def send_mailer(person)
    parser = legacy_first_last_parser(person.name)
    #        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    if parser.can_guess_first_last?
      legacy_carrier(parser.first, parser.last)
    else
      handle_manually(person)
    end
  end

private

  def legacy_first_last_parser(name)
    LegacyFirstLastParser.new(name)
  end
end

Of course, legacy_first_last_parser has the same problem as send_mailer, in that it pulls LegacyFirstLastParser out of thin air. This means that MailSending has to be given the class, so let’s invert those dependencies:

class MailSending
  def initialize(legacy_first_last_parser_class)
    @legacy_first_last_parser_class = legacy_first_last_parser_class
  end

  def send_mailer(person)
    parser = legacy_first_last_parser(person.name)
    if parser.can_guess_first_last?
      legacy_carrier(parser.first, parser.last)
    else
      handle_manually(person)
    end
  end

private

  def legacy_first_last_parser(name)
    @legacy_first_last_parser_class.new(name)
  end
end

This change now requires changing every single use of the MailSending class to pass in the LegacyFirstLastParser class. Sigh.

Is this all better code? Should we have not done this because Regexp and MatchData are in the standard library? The Law certainly doesn’t make that clear.

Just as with all the various SOLID Principles, we really should care about keeping the coupling of our classes low and the cohesion high, but no Law is going to guide is to the right decision, because it lacks subtly and nuance. It also doesn’t provide much help once we have a working understanding of coupling and cohesion. When a team aligns on what those mean, code can discussed directly—you don’t need a Law to help have that discussion and, in fact, talking about it is a distraction.

Suppose we kept our discussion of send_mailer to just coupling. It’s pretty clear that coupling to the language’s standard library is not a real problem. We’ve chosen Ruby, switching programming languages would be a total rewrite, so coupling to Ruby’s standard library is fine and good.

Consider discussing coupling around determine_tax_method. We might have decided that since people, addresses, and countries are central concepts in our app, code that’s coupled to them and their interrelationship is generally OK. If these concepts are stable, coupling to them doesn’t have a huge downside. And the domain should be stable.

Damn the Law.

Tuesday, 21. January 2020

Fast Ruby Blog

The Complete Guide to Migrate to Strong Parameters

Migrating from Protected Attributes to Strong Parameters in a Rails project can be a huge step of the upgrade process. Especially when we are upgrading a large application. This guide is meant to help you tackle that step faster and with a lot less pain.

Protected Attributes & Strong Parameters

To give you a bit of context, let's recap what Protected Attributes and Strong Parameters actually are. They are two different Rails implementations for protecting attributes from end-user injection (a.k.a. Mass Assignment)

To understand what the benefits are of Strong Parameters over Protected Attributes, I recommend checking this RailsCasts episode.

Protected Attributes was part of the Rails core since the beginning of it until Rails 3.2. In Rails 4.0 they introduced Strong Parameters as a replacement of it, and it has been part of the core since then. Before that it was possible to use it through the strong_parameters gem.

After Rails 4.0 came out with the implementation of Strong Parameters, we were able to have backwards compatibility with Protected Attributes by using the protected_attributes gem. That way you could have the latest version of Rails without migrating to Strong Parameters.

That gem was supported by the Rails team until the release of Rails 5.0. After that we were forced to migrate to Strong Parameters. Once that happened we started to get unofficial forks of protected_attributes that support the latest version of Rails, like protected_attributes_continued.

At this point we strongly recommend fully migrating to Strong Parameters, since the available options to keep Protected Attributes alive have very limited support and can encounter security issues.

Migration

The migration consists of moving the Mass Assignment restrictions from the models to the controllers. That means removing the attr_accessible and attr_protected calls from your models and adding a new method to your models' controllers to handle the parameters.

In a simple example, this is how a model and controller look using Protected Attributes:

# app/models/user.rb

class User < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email

  # ...
end
# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def create
    User.create(params[:user])
  end

  # ...
end

This is how it should look after migrating to Strong Parameters:

# app/models/user.rb

class User < ActiveRecord::Base
  # ...
end
# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def create
    User.create(user_params)
  end

  # ...

  private

    def user_params
      params.require(:user).permit(:first_name, :last_name, :email)
    end
end

Sometimes you'll find that the logic is more complex than that; but most of the time moving the parameters is straightforward. The main issue is that it takes a lot of time when you have a large number of models. That's why we created rails_upgrader. This gem will help you speed up the migration process by automating some of it.

Once you install the gem in your project you can run any of these commands in the console:

  • rails_upgrader go (attempt to upgrade your models and controllers in place)
  • rails_upgrader dry-run (write strong parameter migrations in the terminal)
  • rails_upgrader dry-run --file (write strong parameter migrations to all_strong_params.rb file)

Another useful gem is moderate_parameters. This gem can be handy to determine what data is originating from within the app and what is coming from the internet. I recommend taking a look at it as well.

Conclusion

Migrating to Strong Parameters can be a tedious process but with the tools and resources that we presented here, it can be a lot easier.

If you need some more guidance on upgrading your Rails application check out our free eBook: The Complete Guide to Upgrade Rails

Monday, 20. January 2020

Vladimir Makarov – Red Hat Developer

MIR: A lightweight JIT compiler project

For the past three years, I’ve been participating in adding just-in-time compilation (JIT) to CRuby. Now, CRuby has the method-based just-in-time compiler (MJIT), which improves performance for non-input/output-bound programs.

The most popular approach to implementing a JIT is to use LLVM or GCC JIT interfaces, like ORC or LibGCCJIT. GCC and LLVM developers spend huge effort to implement the optimizations reliably, effectively, and to work on a lot of targets. Using LLVM or GCC to implement JIT, we can just utilize these optimizations for free. Using the existing compilers was the only way to get JIT for CRuby in the short time before the Ruby 3.0 release, which has the goal of improving CRuby performance by three times.

So, CRuby MJIT utilizes GCC or LLVM, but what is unique about this JIT?

MJIT does not use existing compiler JIT interfaces. Instead, it uses C as an interface language without losing compilation speed. Practically the same compilation speed as with the existing JIT interfaces is achieved by using precompiled headers and a memory filesystem.

Choosing C as a JIT interface language significantly simplifies JIT implementation, maintenance, and debugging. Doing so also makes the JIT independent from a particular C compiler. Precompiled headers are a pretty standard feature of modern C/C++ compilers.

Disadvantages of GCC/LLVM-based JIT

Enough about the advantages of GCC/LLVM-based JITs. Let us speak about the disadvantages:

  • GCC/LLVM-based JITs are big.
  • Their compilation speed can be slow.
  • It is hard to implement combined optimizations of code written in different programming languages.

Regarding the last point, in the case of CRuby, Ruby and C must be optimized together as you can be using implementation languages of different Ruby methods. Let us consider these disadvantages in more detail.

GCC/LLVM size

First of all, GCC and LLVM are big compared to CRuby. How big? If we ask SLOCCount, GCC and LLVM sources are about three times larger than CRuby, as shown in Figure 1. CRuby is already a big project by itself, with its source consisting of more than 1.5 million lines of code:

The relative source code sizes of GCC, CRuby, and the LLVM option.

Figure 1: CRuby’s source code size compared to GCC’s and LLVM’s source code size.

As for the machine code, GCC and LLVM binaries are much bigger, from 7 to 18 times bigger, as shown in Figure 2:

The relative machine code sizes of GCC, CRuby, and LLVM.

Figure 2: CRuby’s machine code is significantly smaller than GCC’s and LLVM’s machine code.

Imagine adding a JIT for a simple language with LLVM. You can easily increase its interpreter binary code size by a hundred times. CRuby with the current JIT requires much less memory than JRuby or Graal/TruffleRuby. Still, the large code size can be a serious problem for cloud, IoT, or mobile environments.

GCC/LLVM compilation speed

Second, GCC/LLVM compilation speed is slow. It might feel like 20ms on a modern Intel CPU for a method compilation with GCC/LLVM is short, but for less powerful but widely used CPUs this value can be a half-second. For example, according to the SPEC2000 176.gcc benchmark, the Raspberry PI3 B3+ CPU is about 30 times slower than the Intel i7-9700K (score 320 vs. 8520).

We need JIT even more on slow machines, but JIT compilation becomes intolerably slow for these. Even on fast machines, GCC/LLVM-based JITs can be too slow in environments like MinGW. Faster JIT speed can also help achieve desirable JIT performance through aggressive adaptive optimization and function inlining.

What might a faster JIT compilation look like? A keynote about the Java Falcon compiler at the 2017 LLVM developers conference suggested about 100ms per method for an LLVM-based JIT compiler and one millisecond for the faster tier-one JVM compiler. Answering a question about using LLVM for a Python JIT implementation, the speaker (Philip Reems) said that you first need a tier-one compiler implementation. With MJIT for Ruby, we went from the opposite direction by implementing a tier two compiler first.

So, why is GCC/LLVM compilation slow? Here is a full list of GCC-8 backend passes presented in their execution order, as shown in Figure 3:

Figure 3: GCC-8 backend passes presented in their execution order.

There are 321 compiler passes and 239 of them are unique. This fact probably reflects that the GCC community has had more than 600 contributors. Some of these passes are repeated runs of the same optimizations (e.g., dead code elimination). Many compiler passes traverse the intermediate representation (IR) more than once. For example, the register allocator traverses it at least eight times. Running all of these passes requires a lot of time. There are those who think that by switching off most of the passes we can speed up GCC/LLVM compilation proportionally. Unfortunately, this is not true.

The biggest problem for using GCC and LLVM in a lightweight JIT compiler is long initialization time. For small code, initialization can take a majority of the compilation time, and small-method code is a typical scenario for Ruby and other programs in other dynamic high-level languages.

The following diagram illustrates the long startup time of GCC-8/LLVM-8 on an Intel i7-9700K under Fedora Core 29, as shown in Figure 4:

GCC-8/LLVM-8 takes a long time to start up on an Intel i7-9700K under Fedora Core 29.

Figure 4: GCC-8/LLVM-8 startup time on an Intel i7-9700K under Fedora Core 29.

So, you can not switch off optimizations and proportionally speed up GCC and Clang.

Function inlining with GCC/LLVM

Function inlining is the most important optimization for better JIT performance. Method calls are expensive and inlining permits optimizations in a bigger scope.

When both are written in Ruby, inlining one method into another one is a not a problem. We can do this on the VM instruction level or when we generate machine code. The problem is inlining a method written on C into a method written on Ruby or vice versa.

Here is a small example:

  x = 2
  10.times {x *= 2}

Ruby code interpreted by the CRuby VM calls the method times, which is implemented in C. This C code repeatedly calls another Ruby method interpreted by the VM and implementing a multiplication operation, as shown in Figure 5:

How the times method works with CRuby.

Figure 5: The times method through CRuby.

How can we integrate all three parts of the code into one generated function? Let us consider the current structure of MJIT, as shown in Figure 6:

MJIT's structure.

Figure 6: MJIT’s structure.

A C function that implements a standard Ruby method and can be inlined should be in the precompiled header. The more code we insert into the environment header, the slower the MJIT precompiled header generation, and thus the slower the MJIT startup. Another consequence is slower JIT code generation because of bigger precompiled header.

We could also rewrite C code in Ruby. Sometimes this could work, but in the majority of cases, doing so would result in slower generated code.

Lightweight JIT compiler

After analyzing the current disadvantages of MJIT, I came to the conclusion that a lightweight JIT compiler could solve these issues. I think the lightweight JIT compiler should be either an addition to the existing MJIT compiler or the only JIT compiler in cases where the current one does not work.

There is another reason for the lightweight JIT compiler. I believe this compiler could be a good solution for MRuby JIT and would help to expand Ruby usage from a mostly server market to the mobile and IoT markets.

So, last year I started to work on a lightweight JIT compiler mostly in my spare time. Because I’d like to use the JIT compiler not only for Ruby, I decided to make it a universal JIT compiler and a standalone project.

MIR

The central notion of JIT is a well-defined intermediate language called Medium Internal Representation, or MIR. You can find the name in Steven Muchnik’s famous book “Advanced Compiler Design and Implementation.” The Rust team also uses this term for a Rust intermediate language. Still, I decided to use the same name because I like it: MIR means “peace” and “world” in Russian.

MIR is strongly typed and flexible enough. MIR in different forms is capable of representing machine code for both CISC and RISC processors. Although this lightweight JIT compiler is a standalone project, I am planning to try it first with CRuby or MRuby JIT.

To get a better understanding of MIR, let us consider the Eratosthenes prime sieve algorithm code as an example. Here is the C code for sieve:

#define Size 819000
int sieve (int iter) {
  int i, k, prime, count, n; char flags[Size];

  for (n = 0; n < iter; n++) {
    count = 0;
    for (i = 0; i < Size; i++)
      flags[i] = 1;
    for (i = 0; i < Size; i++)
      if (flags[i]) {
        prime = i + i + 3;
        for (k = i + prime; k < Size; k += prime)
          flags[k] = 0;
        count++;
      }
  }
  return count;
}

And here is the MIR textual representation for the same code. There are no hard registers in MIR, only typed variables. The calling convention is also hidden:

m_sieve:  module
         export sieve
sieve:    func i32, i32:iter
         local i64:flags, i64:count, i64:prime, i64:n, i64:i, i64:k, i64:temp
         alloca flags, 819000
         mov n, 0
loop:
         bge fin, n, iter
         mov count, 0;   mov i, 0
loop2:
         bgt fin2, i, 819000
         mov ui8:(flags, i), 1;   add i, i, 1
         jmp loop2
fin2:
         mov i, 0
loop3:
         bgt fin3, i, 819000
         beq cont3, ui8:(flags,i), 0
         add temp, i, i;   add prime, temp, 3;   add k, i, prime
loop4:
         bgt fin4, k, 819000
         mov ui8:(flags, k), 0;   add k, k, prime
         jmp loop4
fin4:
         add count, count, 1
cont3:
         add i, i, 1
         jmp loop3
fin3:
         add n, n, 1;  jmp loop
fin:
         ret count
         endfunc
         endmodule

The first operands in the func pseudo-instruction are the function return types (a MIR function can return multiple values), and after that, all function arguments are declared. Local variables are declared as 64-bit integers through the local pseudo-instruction.

Lightweight JIT compiler project goals

I set performance goals for the JIT compiler. Compared to GCC with -O2, this compiler should have 100 times faster compilation, 100 times faster start-up, and 100 times smaller code size. As for the generated code performance, I decided that it should be at least 70% of GCC -O2 performance.

The implementation should be simple too at less than 10K C lines, because I want wider adoption of this tool. Simple code is easier to learn and maintain. I’d like also to avoid any external dependencies for this project. At the end of the article, you can see the actual results I have now.

How to achieve these performance goals

Optimizing compilers are big and complex because they are trying to improve any code, including rare edge cases. Because they do a lot of things, compilation speed becomes important. These compilers use the fastest algorithms and data structures for compiled programs of different sizes, from small to huge ones, even if the algorithms and data structures are complicated.

So, to achieve our goals, we need to use a few of the most valuable optimizations, optimize only frequently occurring cases, and use algorithms with the best combination of simplicity and performance.

So what are the most valuable optimizations? The most important optimizations are those for effectively exploiting the most commonly used CPU resources: instructions and registers. Therefore, the most valuable optimizations are good register allocation (RA) and instruction selection.

Recently, I did an experiment by switching on only a fast and simple RA and combiner in GCC. There are no options to do this, I needed to modify GCC. (I can provide a patch if somebody is interested.) Compared to hundreds of optimizations in GCC-9.0 with -O2, these two optimizations achieve almost 80% performance on an Intel i7-9700K machine under Fedora Core 29 for real-world programs through SpecCPU, one of the most credible compiler benchmark suites:

SPECInt2000 Est. GCC -O2 GCC -O0 + simple RA + combiner
-fno-inline 5458 4342 (80%)
-finline 6141 4339 (71%)

You might ask, “How is this possible? What do the other numerous optimizations do?” Having worked for many years on optimizing compilers, I would like to take this opportunity to say how difficult it is to improve the performance of well-established optimizing compilers.

The reality of optimizing compiler performance

In the integrated circuit world, there is Moore’s law, which says that the number of transistors on the circuit doubles every 18 months.

In the optimizing compiler world, people like to mention Proebsting’s law to show how hard it is to improve the performance of generated code. Proebsting’s law says that optimizing compiler developers improve generated code performance by two times every 18 years.

Actually, this “law” is too optimistic, and I never saw that anyone tested this. (It is hard to check because you need to wait 18 years.) So, recently I checked it on SPEC CPU2000. SPEC CPU is one of the most credible benchmark suites used by optimizing compiler developers. It contains a set of CPU-intensive applications from the real world; for example, a specific version of GCC is one of the benchmarks. There are many versions of SPEC CPU: 2000, 2006, and 2017. I used the 2000 version because it does not require days to run it. I used the 17-year-old GCC-3.1 compiler (the first GCC version supporting AMD64) and the recent version GCC-8 in their peak performance modes on the same Intel i7-4790K processor:

GCC 3.1 (-O3) GCC 8 (-Ofast -flto -march=native)
SPECInt2000 w/o eon 4498 5212 (+16%)

The actual performance improvement is only 16%, which is not close to the 100% Proebsting’s law states.

Looking at the progress of optimizing compilers, people believe we should not spend our time on their development at all. Dr. Bernstein, an author of the SipHash algorithm used in CRuby, expressed this point of view in one of his talks.

There is another point of view that we should still try to improve the performance of generated code. By some estimates, in 2013 (before active cryptocurrency mining) computers consumed 10% of all produced electricity. Computer electricity consumption in the year 2040 could easily equal the total electricity production from the year 2016.

A one percent compiler performance improvement in energy-proportional computing (an IT industry goal) means saving 25 terawatt-hours (TWh) out of the 25,000 TWh annual world electricity production.

Twenty-five terawatt-hours is equal to the average annual electricity production of six Hoover dams.

Although the optimizing compiler topic is interesting to me, let us return to the MIR project description.

The current state of the MIR project

Figure 7 shows a diagram detailing the current state of the MIR project:

Current MIR project diagram

Figure 7: The current state of the MIR project.

Currently, I can create MIR through an API or from MIR textual or binary representation. The MIR binary representation is up to 10 times more compact and up to 10 times faster to read than the textual one.

I can interpret MIR code and generate AMD64 machine code in memory from MIR. Recently, I got rid of the only external dependency used by the MIR interpreter, the Libffi foreign function interface library.

I can generate C code from MIR, and I am working on a C-to-MIR compiler, which is about 90% done in my opinion.

Possible future directions for the MIR project

Figure 8 shows the possible development directions for this project:

ossible future development directions for the MIR project.

Figure 8: Possible future development directions for the MIR project.

Generating MIR from LLVM internal representation permits the use of code from different languages implemented with LLVM; for example, Rust or Crystal.

Generating MIR from Java bytecode would allow the same for languages implemented with JVM.

Generating WebAssembly from MIR could allow using MIR code in web browsers. Actually, MIR features are pretty close to these of WebAssembly. The only big difference is that WebAssembly is a stack-based internal representation, and MIR is a register-based one.

There will be many interesting possibilities if all the directions are implemented.

MIR generator

Currently, the most interesting component, at least for me, is the MIR generator producing optimized machine code. Figure 9 shows how it works:

Flow chart for the MIR generator.

Figure 9: The process the MIR generator follows.

Here is the process in more detail. First, we simplify MIR as much as possible, which means only using register indirect memory addressing. It also means that immediate instruction operands are used only in move instructions. During this pass, we also remove unused instructions through local value numbering.

Then, we inline function calls represented by the MIR instructions inline and calland, after that, build the control-flow graph (CFG)—in other words, the basic blocks and edges between them. Next, we transform code to reuse already calculated values in global scope through so-called global common subexpression elimination (GCSE). This step is an important optimization for inlined code.

The next pass mostly removes instructions found redundant on the previous pass through a dead code elimination optimization. Then, we do sparse conditional constant propagation (SCCP). This step is also an important optimization for inlined code when there are constant call arguments. We also find that there can be constant expression values.

Constant values can switch off CFG paths during any execution. Switching off these paths can make other values constant, too. SCCP is a combined optimization, which means its result is better than performing the two separate optimizations of constant propagation and removing unused CFG paths.

After that, we run the machine-dependent code. Most x86 instructions, for example, additionally require the instruction result operand to be the same as one of the input operands. Therefore, the machine-dependent code is run to produce two-operand instructions. This code also generates additional MIR instructions for call parameter passing and returns.

The next step is to find natural loops in the MIR code CFG. This information will be used for the subsequent register allocation. Then, we calculate live information for MIR variables. In the compiler world, this technique is known as a backward data-flow problem.

Once that work is complete, we calculate program points where MIR variables live. This info is used in a fast register allocator that assigns target hard registers, or stack slots, to MIR variables and removes various copy instructions. After the register assignment, we rewrite the MIR code, changing variables to the assigned hard registers or stack slots.

Then, we try to combine pairs of data-dependent MIR instructions into ones whose forms can represent a machine instruction. This is an instruction selection task. This pass also does copy propagation optimization.

After instruction combining, there are usually many instructions whose output is never used. We delete such instructions. And then, finally, we generate machine code in memory. Each MIR instruction is encoded by a machine instruction. For AMD64, the encoding can be complicated.

MIR generator features

These days, most compiler optimizations are implemented for static single assignment form. This is a special form of IR where we have only one assignment to each variable in the function IR. It was invented by IBM researchers a long time ago.

Using SSA simplifies the optimization implementations, but building and destroying this form is expensive. Using SSA for the MIR generator’s short-pass pipeline makes little sense to me, so I don’t use it.

Also, I don’t generate position-independent code. I don’t see any reasons to do this now. Generating such code is more complicated and decreases performance. For the AMD Geode processor (used in one version of the laptop for the One Laptop per Child initiative) the performance decrease achieved 7%. For modern processors, the performance decrease is much smaller but still exists.

Possible ways to compile C to MIR

To start using MIR in CRuby, I need a C-to-MIR compiler. There are several ways to implement this feature. I could implement an LLVM IR-to-MIR compiler or write a GCC port targeting MIR, but doing this would create a big dependency on a particular external project. It is not an easy task either. Once, I worked in a team that specialized in porting GCC to new targets and the standard estimation was at least six months of work to create a simple “hello, world” program.

On the other hand, people have written small C compilers pretty quickly. Here, I can mention the Tiny C Compiler, and the recent 8cc and 9cc compiler projects.

So, I decided to write my own C-to-MIR compiler first. This compiler should implement standard C11 without optional features, such as variable arrays, complex numbers, and atomic data.

The major goal is simplicity, not speed. Again, doing this makes studying the code easier for other people and reduces the effort required to maintain it.

C-to-MIR compiler

Simplicity is usually achieved by dividing tasks into small, manageable subtasks. There is even an extreme nanopass compiler design used for studying compiler topics in education.

My approach to the C compiler implementation is classic division on four passes of approximately the same size, as shown in Figure 10:

Flow chart for the C-to-MIR compiler.

Figure 10: The C-to-MIR compiler approach.

I don’t use any tools for the compiler, like YACC. Also, I don’t modify the ANSI standard grammar, although it is ambiguous. I use a parsing expression grammar (PEG) manual parser. It is a parser with moderate backtracking and it is simple and small but a bit slower than deterministic parsers.

The MIR-to-C compiler is mostly implemented. It passes around 1,000 tests from different C test suites. Recently, I achieved an important milestone: a successful bootstrap. The new c2m compiles its own sources and generates a MIR binary. The execution of this MIR binary processes c2m sources again and generates another MIR binary, and the two MIR binary files are identical (option -el of the c2m invocation that follows means the execution of generated MIR code through lazy machine code generation):

   cc -O3 -fno-tree-sra -std=gnu11 -Dx86_64 -I. mir-gen.c c2mir.c c2mir-driver.c mir.c -ldl -o c2m
  ./c2m -Dx86_64 -I. mir-gen.c c2mir.c c2mir-driver.c mir.c -o 1.bmir
  ./c2m 1.bmir -el -Dx86_64 -I. mir-gen.c c2mir.c c2mir-driver.c mir.c -o 2.bmir

Still, a lot of effort should be made to finish the missing obligatory C11 standard features (e.g., wide characters), achieve full call ABI compatibility, generate more efficient code for switches, and implement the GCC C extensions necessary for the CRuby JIT implementation.

LLVM IR-to-MIR compiler

I am also working on an LLVM IR-to-MIR compiler. Currently, I am focusing on translating the LLVM IR produced by Clang from standard C code. Doing so will be useful when we want to generate more optimized MIR code and don’t need a fast C compiler (e.g., when building a MIR-based JIT for a programming language).

The only missing part now is the translation of composite values, which are generated by Clang in rare cases when passing small structures in function calls or returning them from function calls. In the future, this translator could be extended to support code generated from other programming languages implemented with LLVM, such as Rust.

I hope that LLVM IR will stabilize in the near future and no extensive maintenance will be necessary.

Possible ways to use MIR for CRuby MJIT

So how do I plan to use the MIR project for CRuby? Figure 11 shows how the current MJIT works:

Diagram showing how the current MJIT works.

Figure 11: How the current MJIT works.

MIR compiler as a tier-one JIT compiler in CRuby

Figure 12 shows how the future MJIT would look after implementing a MIR-based JIT compiler as a tier-one compiler:

How the future MJIT would look after implementing a MIR-based JIT compiler as a tier one compiler.

Figure 12: How the future MJIT would look after implementing a MIR-based JIT compiler as a tier-one compiler.

The blue parts show the new data-flow for MJIT. When building CRuby, we could generate MIR code for the standard Ruby methods written in C. We can load this MIR code as a MIR binary. This part could be done very quickly.

MJIT could create MIR code for a Ruby method through the MIR API. This MIR code could inline the already existing MIR code functions. The corresponding machine code can be generated by the MIR generator. This is a fast method, but we could also generate machine code by translating MIR into C and then using GCC or LLVM. This is a much slower method, but it permits us to use the full spectrum of GCC/LLVM optimizations, plus it permits efficient implementation of inlining C code into Ruby code and vice versa.

MIR compiler as a single JIT compiler in CRuby

For some environments, the MIR JIT compiler could be used as a single JIT compiler. In this case, MIR with MJIT would look as you see in Figure 13:

Diagram showing how the MIR compiler would work as a single JIT compiler in CRuby.

Figure 13: How the MIR compiler would work as a single JIT compiler in CRuby.

MIR compiler and C-to-MIR compiler as a single JIT compiler in CRuby

Instead of directly generating MIR for the JIT compiler, we could generate C code first and translate it into MIR with the C-to-MIR translator. In this case, MJIT would look as you see in Figure 14:

Diagram showing how the MIR compiler combined with the C-to-MIR compiler would work in CRuby.

Figure 14: How the MIR compiler combined with the C-to-MIR compiler would work in CRuby.

I discussed the lightweight JIT compiler project with a few people, and two of them independently wanted to generate C code for JIT. It would make their life easier to use a MIR-based JIT compiler for their own JIT implementations.

The C-to-MIR JIT compiler is pretty small, has a fast startup, and can be used as a library to read the generated C code from memory. Although the C-to-MIR translator was not designed to be fast, it is still about 15 times faster than GCC with -O2. All of this makes such an approach viable.

Current performance results

And finally, here are the current performance results for the MIR generator and interpreter compared to GCC-8.2.1. I used the sieve benchmark on an Intel i7-9700K machine running Fedora Core 29. The sieve C code has no include directives and is only about 30 preprocessed lines:

MIR-gen MIR-interp gcc -O2 gcc -O0
compilation [1] 1.0 (75us) 0.16 (12us) 178 (13.35ms) 171 (12.8ms)
execution [2] 1.0 (3.1s) 5.9 (18.3s) 0.94 (2.9s) 2.05 (6.34s)
code size [3] 1.0 (175KB) 0.65 (114KB) 144 (25.2MB) 144 (25.2MB)
startup [4] 1.0 (1.3us) 1.0 (1.3us) 9310 (12.1ms) 9850 (12.8ms)
LOC [5] 1.0 (16K) 0.56 (9K) 93 (1480K) 93 (1480K)

The compilation speed of the MIR-generator is about 180 times faster than GCC with -O2. It takes 80 microseconds to generate the code for the sieve. And the sieve code generated by the MIR generator is only 6% slower.

The MIR generator’s object size is much smaller than the object size of cc1. MIR generator has a fast startup time and is suitable for use as a tier1 JIT compiler.

Here are the notes for each table row:

  • [1]: Wall time of compilation of sieve code (without any include file and with using the memory file system for GCC).
  • [2]: The best wall time of 10 runs.
  • [3]: The stripped sizes of cc1 for GCC, and the MIR core and interpreter or generator for MIR.
  • [4]: Wall time of object code generation for an empty C file, or of the generation of an empty MIR module through the API.
  • [5]: Based only on the files required for the AMD64 C compiler and the minimal number of files required to create and run MIR code.

Current MIR SLOC distribution

Figure 15 shows a source line distribution for the current version of the MIR project. The MIR-to-C compiler is about 12 thousand lines of C code. MIR core is about nine thousand lines:

A source line distribution for the current version of the MIR project.

Figure 15: A source line distribution for the current version of the MIR project.

The MIR generator is less than five thousand lines.

Machine-dependent code used by the generator is about two thousand lines, so you can estimate the effort required to port the MIR generator to another target. For example, I expect that porting MIR to Aarch64 will take me about one to two months of work.

MIR project competitors

I’ve been studying JITs for a long time. Before starting the MIR project, I thought about adapting the existing code. Here is a comparison of MIR with the simple compiler projects I considered:

Project SLOC License IR type Major Optimizations Output
MIR 16K C MIT non-SSA Inlining, GCSE, SCCP, RA, CP, DCE, LA Binary code
DotGNU LibJIT 80K C LGPL non-SSA Only RA and primitive CP Binary code
.NET RyuJIT 360K C++ MIT SSA MIR ones minus SCCP plus LICM, Range Binary code
QBE 10K C MIT SSA MIR ones plus aliasing minus inlining Assembler
LIBFirm 140K C LGPL2 SSA RA, Inlining, DCE, LICM, CP, LA, Others Assembler
CraneLift 70K Rust Apache SSA DCE, LICM, RA, GVN, CP, LA Binary Code

Here are the abbreviations used in the table:

Others include:

After studying these projects closely, I decided to start the MIR project. The biggest cons of the existing projects were their size, the fact that it would be hard for me to achieve my goals using them, and that I can not control the projects. Also, the smaller source size of the MIR project makes studying the code easier for other people and reduces the effort required to maintain it.

Plans for the MIR project

The MIR project is pretty ambitious. I’ve decided to develop it in an open way because this permits me to receive valuable feedback from many people in the project’s early stages. You can follow the progress of the project on GitHub.

Although MIR is still in the early stages of development, I plan to start using it for a CRuby/MRuby JIT implementation soon. It would be a useful way to improve the MIR compiler implementation and find missing features.

If the use of a MIR-based compiler for Ruby JIT is successful, I will work on the first release of the MIR project code, which I hope will be useful for JIT implementations of other programming languages.

Share

The post MIR: A lightweight JIT compiler project appeared first on Red Hat Developer.


Rails Girls Summer of Code Blog

Alumni Interview with Keziah Naggita

In our new series of Alumni Interviews, we are proud to showcase the interesting paths RGSoC students have gone on to since working on their projects.

First up, Keziah Naggita. As a student in 2016, Keziah worked as part of a remote team on Qutebrowser before going on to coach in 2018. Currently, Keziah is pursuing a PhD in Computer Science at Toyota Technological Institute at Chicago.

When did you first become interested in programming?

I started programming seriously in my second year at university when I interned in the Artificial Intelligence and Data Science lab at Makerere University, Uganda. During the internship, I learnt how to communicate logic using python, which was very interesting.

Which skills did you find most useful during RGSoC?

Writing clean, well-tested code and working with a diverse team.

What challenges did you encounter during the program?

Initially, the big codebase was very intimidating. We patiently and deeply studied module by module to understand the flow and different components of the code, which later made it easier to contribute to the codebase.

Timezone differences were a big challenge because we all had different timezones. Mentors, supervisors, coaches and ourselves lived in different parts of the world. We held several focused meetings, documented our meetings and communicated over Slack to keep communication consistent.

Working with Git is continuously a learning process. Initially, things like making a pull request and squashing commits were a challenge, as time went on, with more and more trials, massive failures and small victories, Git became a part of us.

I am very grateful to RGSoC because this process helped me grow both professionally and personally. And by the time the summer was over, I was more confident in my ability to work with Git, and contributing to Open Source.

How did your participation in RGSoC help you get to where you are today?

RGSoC to me is a reminder of diving into the unknown, moving to unfamiliar territories and making meaningful contributions to the community. At the beginning of RGSoC there were several things I didn’t know how to do, for example, TDD, working with Git and so much more. When I eventually made some significant contributions to qutebrowser, it changed the way I saw myself. I learnt to believe in myself, my ability to learn anything I put my mind to, and trusting the process.

Do you have any advice for new RGSoC students and for women and non-binary people who wish to work in tech?

Believe in your ability to learn and excel at anything. Trust the process, celebrate progress, breath and make wonderful friends in the community. RGSoC avails to you a great support system to fall back on, make use of it! All the great software engineers you see started with “hello world”.

Sunday, 19. January 2020

Lucas Caton

Vídeo: Introdução à JavaScript (JS) em 30 minutos

Aprenda o básico de JavaScript (JS) através do mais recente vídeo que publiquei no meu canal do YouTube:





Abaixo você encontra os arquivos criados/utilizados no vídeo:

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Introdução à JS</title>

    <link rel="stylesheet" href="css/style.css">
  </head>

  <body>
    <main>
      <nav>
        <a href="#" id="theme-toggle">Alterar tema</a>
      </nav>

      <article>
        <h1>The Matrix</h1>

        <h2>Cast</h2>

        <ul class='cast'></ul>

        <p>
          The Matrix is a 1999 science fiction action film[3][4] written and directed by the Wachowskis.[a] It stars Keanu Reeves, Laurence Fishburne, Carrie-Anne Moss, Hugo Weaving, and Joe Pantoliano and is the first installment in the Matrix franchise. It depicts a dystopian future in which humanity is unknowingly trapped inside a simulated reality, the Matrix, created by intelligent machines to distract humans while using their bodies as an energy source.[5] When computer programmer Thomas Anderson, under the hacker alias "Neo", uncovers the truth, he "is drawn into a rebellion against the machines"[5] along with other people who have been freed from the Matrix.
        </p>

        <p>
          The Matrix is an example of the cyberpunk subgenre of science fiction.[6] The Wachowskis' approach to action scenes was influenced by Japanese animation[7] and martial arts films, and the film's use of fight choreographers and wire fu techniques from Hong Kong action cinema influenced subsequent Hollywood action film productions. The film is known for popularizing a visual effect known as "bullet time", where the heightened perception of certain characters is represented by allowing the action within a shot to progress in slow-motion while the camera appears to move through the scene at normal speed, allowing the sped-up movements of certain characters to be perceived normally. While some critics have praised the film for its handling of difficult subjects, others have said the deeper themes are largely overshadowed by its action scenes.
        </p>
      </article>
    </main>

    <script src="js/app.js"></script>
  </body>
</html>
css/style.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background-color: #f5f5f5;
}

main {
  max-width: 500px;
  min-height: 100vh;
  margin: auto;
  padding: 1rem 2rem;
  font-family: sans-serif;
  background-color: white;
  color: #333;
}

article h1 {
  margin: 1rem 0;
  font-size: 3rem;
}

article p {
  font-size: 1.4rem;
  margin: 2rem 0;
}

nav {
  display: flex;
  flex-direction: row-reverse;
}

#theme-toggle {
  text-decoration: none;
  padding: 0.25rem 0.5rem;
  border-radius: 5px;

  background-color: #eee;
  color: #333;
}

ul {
  margin: 1rem 0 0 3rem;
}

/* Dark theme */

body.dark {
  background-color: #222;
}

body.dark main {
  background-color: #444;
  color: #eee;
}

body.dark #theme-toggle {
  background-color: #777;
  color: #ddd;
}
js/app.js
document.querySelector('#theme-toggle').addEventListener('click', (event) => {
  event.preventDefault();

  toggleTheme();
});

function toggleTheme() {
  document.body.classList.toggle('dark');
}
const cast = [
  'Keanu Reeves',
  'Carrie-Anne Moss',
  'Laurence Fishburne',
  'Hugo Weaving'
];

const castList = document.querySelector('.cast');

cast.forEach(person => {
  const item = document.createElement("li");
  const personName = document.createTextNode(person);
  item.appendChild(personName);
  castList.appendChild(item);

  // P.S. Existem formas melhores de fazer ↑ isso,
  // usando um framework tipo React por exemplo :)
});
document.querySelector('main').innerHTML = '';

fetch('https://api.github.com/users/lucascaton/repos?per_page=100')
  .then(response => response.json())
  .then(repos => repos.forEach(repo => {
    let item = document.createElement("li");
    var repoName = document.createTextNode(repo.name);
    item.appendChild(repoName);
    document.querySelector('main').appendChild(item);
  }));

Se curtiu o vídeo, não deixe de se inscrever no canal! 😉


Riding Rails

This week in Rails - Rack 2.1 released, disallowed deprecations, and more!

Hello, this is Andrew, bringing you the latest news from the Ruby on Rails world!

18 contributors to Rails in past week

There have been 18 contributors to Rails in the second full week of 2020! 

Rack 2.1.0 and 2.1.1 released

These releases add support for the SameSite=None cookie value, new HTTP status codes, bug fixes, and several other exciting changes and additions. Updates to Rails following the release have also begun.

Check out the Rack changelog to learn more.

Introduce Active Support Disallowed Deprecations

This addition allows the configuration of rules to match deprecation warnings that should not be allowed and ActiveSupport::Deprecation#disallowed_behavior, which specifies the behavior to be used when a disallowed deprecation warning is matched.

Stop individual Action Cable streams

Channels with multiple subscriptions can now stop following individual streams. Before this change, the only option was to stop all streams.

Remove an empty line from generated migration

This fix prevents an extra newline from getting added in generated migrations.

That’s it for this week, till next time! 


The Life of a Radar

Getting Started with Rails: Extended Edition

This guide is an extended edition of the offical Getting Started with Rails guide.

This extended version has the following additions:

  • A re-worked introduction to routing, showing clearer matching between routes and controllers.
  • Creates and uses a model right after building the first route.
  • Examples of HTML output generated by Rails helpers such as link_to, form_with and text_field.
  • Beef up explainations of routing helpers, especially article_comment_path.
  • Beef up explainations in general.

This guide covers getting up and running with Ruby on Rails.

After reading this guide, you will know:

  • How to install Rails, create a new Rails application, and connect your application to a database.
  • The general layout of a Rails application.
  • The basic principles of MVC (Model, View, Controller) and RESTful design.
  • How to quickly generate the starting pieces of a Rails application.

Guide Assumptions

This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience with Rails.

Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are several curated lists of online resources for learning Ruby:

Be aware that some resources, while still excellent, cover versions of Ruby as old as 1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day development with Rails.

What is Rails?

Rails is a web application development framework written in the Ruby programming language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun.

Rails is opinionated software. It makes the assumption that there is a "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience.

The Rails philosophy includes two major guiding principles:

  • Don't Repeat Yourself: DRY is a principle of software development which states that "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". By not writing the same information over and over again, our code is more maintainable, more extensible, and less buggy.
  • Convention Over Configuration: Rails has opinions about the best way to do many things in a web application, and defaults to this set of conventions, rather than require that you specify minutiae through endless configuration files.

Creating a New Rails Project

The best way to read this guide is to follow it step by step. All steps are essential to run this example application and no additional code or steps are needed.

By following along with this guide, you'll create a Rails project called blog, a (very) simple weblog. Before you can start building the application, you need to make sure that you have Rails itself installed.

TIP: The examples below use $ to represent your terminal prompt in a UNIX-like OS, though it may have been customized to appear differently. If you are using Windows, your prompt will look something like c:\source_code>

Installing Rails

Before you install Rails, you should check to make sure that your system has the proper prerequisites installed. These include Ruby and SQLite3.

Open up a command line prompt. On macOS open Terminal.app, on Windows choose "Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a dollar sign $ should be run in the command line. Verify that you have a current version of Ruby installed:

$ ruby -v
ruby 2.7.0

Rails requires Ruby version 2.5.0 or later. If the version number returned is less than that number (such as 2.3.7, or 1.8.7), you'll need to install a fresh copy of Ruby.

TIP: To quickly install Ruby and Ruby on Rails on your system in Windows, you can use Rails Installer. For more installation methods for most Operating Systems take a look at ruby-lang.org.

If you are working on Windows, you should also install the Ruby Installer Development Kit.

You will also need an installation of the SQLite3 database. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. On Windows, if you installed Rails through Rails Installer, you already have SQLite installed. Others can find installation instructions at the SQLite3 website. Verify that it is correctly installed and in your PATH:

$ sqlite3 --version

The program should report its version.

To install Rails, use the gem install command provided by RubyGems:

$ gem install rails

To verify that you have everything installed correctly, you should be able to run the following:

$ rails --version

If it says something like "Rails 6.0.0", you are ready to continue.

Creating the Blog Application

Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself.

To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type:

$ rails new blog

This will create a Rails application called Blog in a blog directory and install the gem dependencies that are already mentioned in Gemfile using bundle install.

NOTE: If you're using Windows Subsystem for Linux then there are currently some limitations on file system notifications that mean you should disable the spring and listen gems which you can do by running rails new blog --skip-spring --skip-listen.

TIP: You can see all of the command line options that the Rails application builder accepts by running rails new -h.

After you create the blog application, switch to its folder:

$ cd blog

The blog directory has a number of auto-generated files and folders that make up the structure of a Rails application. Most of the work in this tutorial will happen in the app folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:

File/Folder Purpose
app/ Contains the controllers, models, views, helpers, mailers, channels, jobs, and assets for your application. You'll focus on this folder for the remainder of this guide.
bin/ Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy, or run your application.
config/ Configure your application's routes, database, and more. This is covered in more detail in Configuring Rails Applications.
config.ru Rack configuration for Rack based servers used to start the application. For more information about Rack, see the Rack website.
db/ Contains your current database schema, as well as the database migrations.
Gemfile
Gemfile.lock
These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the Bundler website.
lib/ Extended modules for your application.
log/ Application log files.
package.json This file allows you to specify what npm dependencies are needed for your Rails application. This file is used by Yarn. For more information about Yarn, see the Yarn website.
public/ The only folder seen by the world as-is. Contains static files and compiled assets.
Rakefile This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.
README.md This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.
storage/ Active Storage files for Disk Service. This is covered in Active Storage Overview.
test/ Unit tests, fixtures, and other test apparatus. These are covered in Testing Rails Applications.
tmp/ Temporary files (like cache and pid files).
vendor/ A place for all third-party code. In a typical Rails application this includes vendored gems.
.gitignore This file tells git which files (or patterns) it should ignore. See GitHub - Ignoring files for more info about ignoring files.
.ruby-version This file contains the default Ruby version.

Hello, Rails!

To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running.

Starting up the Web Server

You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running the following in the blog directory:

$ bin/rails server

TIP: If you are using Windows, you have to pass the scripts under the bin folder directly to the Ruby interpreter e.g. ruby bin\rails server.

TIP: JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence of a runtime you will see an execjs error during asset compression. Usually macOS and Windows come with a JavaScript runtime installed. therubyrhino is the recommended runtime for JRuby users and is added by default to the Gemfile in apps generated under JRuby. You can investigate all the supported runtimes at ExecJS.

This will fire up Puma, a web server distributed with Rails by default. To see your application in action, open a browser window and navigate to http://localhost:3000. You should see the Rails default information page:

Yay! You're on Rails! screenshot

TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most UNIX-like systems including macOS this will be a dollar sign $. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server.

The "Yay! You're on Rails!" page is the smoke test for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page.

Say "Hello", Rails

To get Rails saying "Hello", you need to create at minimum a route, a controller and a view.

A controller's purpose is to receive specific requests for the application. Routing decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different actions. Each action's purpose is to collect information to provide it to a view.

A view's purpose is to display this information in a human readable format. An important distinction to make is that the controller, not the view, is where information is collected. The view should just display that information. By default, view templates are written in a language called eRuby (Embedded Ruby) which is processed by the request cycle in Rails before being sent to the user.

When we make a request to our Rails applications, we do so by making a request to a particular route. So to start off, we'll start with a route. Let's create one now in config/routes.rb:

Rails.application.routes.draw do
  get "/articles", to: "articles#index"

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

This is your application's routing file which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions.

The line that we have just added says that we are going to match a GET /welcome request to welcome#index. This string passed as the to option represents the controller and action that will be responsible for handling this request.

Controllers are classes that group together common methods for handling a particular resource. The methods inside controllers are given the name "actions", as they act upon requests as they come in.

To create a new controller, you will need to run the "controller" generator and tell it you want a controller called "articles" with an action called "index", just like this:

$ bin/rails generate controller articles index

Rails will create several files and a route for you.

create  app/controllers/articles_controller.rb
  route  get 'articles/index'
invoke  erb
create    app/views/articles
create    app/views/articles/index.html.erb
invoke  test_unit
create    test/controllers/articles_controller_test.rb
invoke  helper
create    app/helpers/articles_helper.rb
invoke    test_unit
invoke  assets
invoke    scss
create      app/assets/stylesheets/articles.scss

Most important of these are is of course the controller, located at app/controllers/articles_controller.rb.

Let's look at that controller now:

class ArticlesController < ApplicationController
  def index
  end
end

This controller defines a single action, or "method" in common Ruby terms, called index. This action is where we would define any logic that we would want to happen when a request comes in to this action. Right at this moment, we don't want this action to do anything, and so we'll keep it blank for now.

When an action is left blank like this, Rails will default to rendering a view that matches the name of the controller, and the name of the action. That view is going to be app/views/articles/index.html.erb.

Open the app/views/articles/index.html.erb file in your text editor. Delete all of the existing code in the file, and replace it with the following single line of code:

<h1>Hello, Rails!</h1>

If we go back to our browser and make a request to http://localhost:3000/articles, we'll see our text appear on the page.

Setting the Application Home Page

Now that we have made the route, controller, action and view, let's make a small change to our routes. In this application, we're going to change it so that our message appears at http://localhost:3000/ and not just http://localhost:3000/articles. At the moment, at http://localhost:3000 it still says "Yay! You're on Rails!".

To change this, we need to tell our routes file where the root path of our application is.

Open the file config/routes.rb in your editor.

Rails.application.routes.draw do
  get "/articles", to: "articles#index"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Underneath the line that is there, add this line:

Rails.application.routes.draw do
  root to: "articles#index"
end

A slightly shorter way of writing the same thing is:

Rails.application.routes.draw do
  get "/articles", to: "articles#index"
  root "articles#index"
end

This root method defines a root path for our application. The root method tells Rails to map requests to the root of the application to the ArticlesController index action.

Launch the web server again if you stopped it to generate the controller (rails server) and navigate to http://localhost:3000 in your browser. You'll see the "Hello, Rails!" message you put into app/views/articles/index.html.erb, indicating that this new route is indeed going to ArticleController's index action and is rendering the view correctly.

TIP: For more information about routing, refer to Rails Routing from the Outside In.

Creating a model

So far, we have seen routes, controllers, actions and views within our Rails application. All of these are conventional parts of Rails applications and it is done this way to follow the MVC pattern. The MVC pattern is an application design pattern which makes it easy to separate the different responsibilities of applications into easy to reason about pieces.

So with "MVC", you might guess that the "V" stands for "View" and the "C" stands for controller, but you might have trouble guessing what the "M" stands for. This next section is all about that "M" part, the model.

A model is a class that is used to represent data in our application. In a plain-Ruby application, you might have a class defined like this:

class Article
  attr_reader :title, :body

  def initialize(title:, body:)
    @title = title
    @body = body
  end
end

Models in a Rails application are designed for this purpose too: to represent particular data.

Models have another purpose in a Rails application too though. They're also used to interact with the application's database. In this section, we're going to use a model to put data into our database and to pull that data back out.

To start with, we're going to need to generate a model. We can do that with the following command:

$ bin/rails g model article title:string body:text

NOTE: The model name here is singular, because model classes are classes that are used to represent single instances. To help remember this rule, in a Ruby application to start building a new object, you would define the class as Article, and then do Article.new, not Articles and Articles.new.

When this command runs, it will generate the following files:

invoke  active_record
create    db/migrate/[timestamp]_create_articles.rb
create    app/models/article.rb
invoke    test_unit
create      test/models/article_test.rb
create      test/fixtures/articles.yml

The two files we'll focus on here are the migration (the file at db/migrate) and the model.

A migration is used to alter the structure of our database, and it is written in Ruby. Let's look at this file now, db/migrate/[timestamp]_create_articles.rb.

class CreateArticles < ActiveRecord::Migration[6.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

This file contains Ruby code to create a table within our application's database. Migrations are written in Ruby so that they can be database-agnostic -- regardless of what database you use with Rails, you'll always write migrations in Ruby.

Inside this migration file, there's a create_table method that defines how the articles table should be constructed. This method will create a table in our database that contains an id auto-incrementing primary key. That means that the first record in our table will have an id of 1, and the next id of 2, and so on. Rails assumes by default this is the behaviour we want, and so it does this for us.

Inside the block for create_table, we have two fields, title and body. These were added to the migration automatically because we put them at the end of the rails g model call:

$ bin/rails g model article title:string body:text

On the last line of the block is t.timestamps. This method defines two additional fields in our table, called created_at and updated_at. When we create or update model objects, these fields will be set respectively.

The structure of our table will look like this:

id title body created_at updated_at
 

To create this table in our application's database, we can run this command:

rails db:migrate

This command will show us output indicating that the table was created:

== 20200118233119 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0018s
== 20200118233119 CreateArticles: migrated (0.0018s) ==========================

Now that we have a table in our application's database, we can use the model to interact with this table.

To use the model, we'll use a feature of Rails called the console. The console allows us write code like we might in irb, but the code of our application is available there too. Let's launch the console with this command:

rails console

Or, a shorter version:

rails c

When we launch this, we should see an irb prompt:

Loading development environment (Rails 6.0.2.1)
irb(main):001:0>

In this prompt, we can use our model to initialize a new Article object:

irb(main):001:0> article = Article.new(title: "Hello Rails", body: "I am on Rails!")

When we use Article.new, it will initialze a new Article object in the console. This object is not saved to the database at all, it's just available in the console so far. To save the object to the database, we need to call save:

irb(main):002:0> article.save

This command will show us the following output:

(0.1ms)  begin transaction
Article Create (0.4ms)  INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms)  commit transaction
=> true

This output shows an INSERT INTO "articles"... database query. This means that our article has been successfully inserted into our table.

If we take a look at our article object again, an interesting thing has happened:

irb(main):003:0> article
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

Our object now has the id, created_at and updated_at fields set. All of this happened automatically for us when we saved this article.

If we wanted to retrieve this article back from the database later on, we can do that with find, and pass that id as an argument:

irb(main):004:0> article = Article.find(1)
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

A shorter way to add articles into our database is to use Article.create, like this:

irb(main):005:0> Article.create(title: "Post #2", body: "Still riding the Rails!")

This way, we don't need to call new and then save.

Lastly, models provide a method to find all of their data:

irb(main):006:0> articles = Article.all
 #<ActiveRecord::Relation [
   #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">,
   #<Article id: 2, title: "Post #2", body: "Still riding the Rails!", created_at: "2020-01-18 23:53:45", updated_at: "2020-01-18 23:53:45">]>

This method returns an ActiveRecord::Relation object, which you can think of as a super-powered array. This array contains both of the topics that we have created so far.

As you can see, models are very helpful classes for interacting with databases within Rails applications. Models are the final piece of the "MVC" puzzle. Let's look at how we can go about connecting all these pieces together into a cohesive whole.

Getting Up and Running

Now that you've seen how to create a route, a controller, an action, a view and a model, let's connect these pieces together.

Let's go back to app/controllers/articles_controller.rb now. We're going to change the index action here to use our model.

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

Controller actions are where we assemble all the data that will later be displayed in the view. In this index action, we're calling Article.all which will make a query to our database and retrieve all of the articles, storing them in an instance variable: @articles.

We're using an instance variable here for a very good reason: instance variables are automatically shared from controllers into views. So to use this @articles variable in our view to show all the articles, we can write this code in app/views/articles/index.html.erb:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li><%= article.title %></li>
  <% end %>
</ul>

We've now changed this file from using just HTML to using HTML and ERB. ERB is a language that we can use to run Ruby code.

There's two types of ERB tag beginnings that we're using here: <% and <%=. The <% tag means to evalulate some Ruby code, while the <%= means to evalulate that code, and then to output the return value from that code.

In this view, we do not want the output of articles.each to show, and so we use a <%. But we do want each of the articles' titles to appear, and so we use <%=.

When we start an ERB tag with either <% or <%=, it can help to think "I am now writing Ruby, not HTML". Anything you could write in a regular Ruby program, can go inside these ERB tags.

When the view is used by Rails, the embedded Ruby will be evalulated, and the page will show our list of articles. Let's go to http://localhost:3000 now and see the list of articles:

List of articles

If we look at the source of the page in our browser view-source:http://localhost:3000/, we'll see this part:

    <h1>Articles</h1>

<ul>
    <li>Hello Rails</li>
    <li>Post #2</li>
</ul>

This is the HTML that has been output from our view in our Rails application.

Here's what's happened to get to this point:

  1. Our browser makes a request: GET http://localhost:3000
  2. The Rails application receives this request
  3. The router sees that the root route is configured to route to the ArticlesController's index action
  4. The index action uses the Article model to find all the articles
  5. Rails automatically renders the app/views/articles/index.html.erb view
  6. The view contains ERB (Embedded Ruby). This code is evalulated, and plain HTML is returned.
  7. The server sends a response containing that plain HTML back to the browser.

Here's a flowchart of the above steps:

Application flowchart

We've now successfully connected all the different parts of our Rails application together: the router, the controller, the action, the model and the view. With this connection, we have finished the first action of our application.

Let's move on to the second action!

Viewing an Article

For our second action, we want our application to show us the details about an article, specifically the article's title and body:

Single Article View

We'll start in the same place we started with the index action, which was in config/routes.rb. We'll add a new route for this page. Let's change our routes file now to this:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/:id", to: "articles#show"
end

This route is another get route, but it has something different in it: :id. This syntax in Rails routing is called a parameter, and it will be available in the show action of ArticlesController when a request is made. A request to this action will use a route such as http://localhost:3000/articles/1 or http://localhost:3000/articles/2.

This time, we're still routing to the ArticlesController, but we're going to the show action of that controller instead of the index action.

Let's look at how to add that show action to the ArticlesController. We'll open app/controllers/articles_controller.rb and add it in, under the index action:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end
end

When a request is made to this show action, it will be made to a URL such as http://localhost:3000/articles/1. Rails sees that the last part of that route is a dynamic parameter, and makes that parameter available for us in our controller through the method params. We use params[:id] to access that parameter, because back in the routes file we called the parameter :id. If we used a name like :article_id in the routes file, then we would need to use params[:article_id] here too.

The show action finds a particular article with that ID. Once it has that, it needs to then display that article's information, which will do by attempting to use a view at app/views/articles/show.html.erb. Let's create that file now and add this content:

<h1><%= @article.title %></h1>

<%= @article.body %>

Now when we go to http://localhost:3000/articles/1 we will see the article:

Single Article View

Excellent! We now have our second action working in our controller. But in order to navigate to it, we have to manually type in http://localhost:3000/articles/1. That seems a bit silly. Let's change our application a little, so that we can navigate to an article by clicking a link from the list of articles.

To add the link to an article, we need to change app/views/articles/index.html.erb, which currently looks like this:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li><%= article.title %></li>
  <% end %>
</ul>

This code will render an li element for each of the articles, and that element contains the title of the article. But we can't click on the title to go to an article yet! To make that happen, we need to use an a tag:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href='/articles/<%= article.id %>'>
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

This a tag will provide us with a link to the specific article. If we go back to http://localhost:3000/, we'll see that we can now click on the articles:

Articles list with links

Clicking either of these links will take us to the relevant article:

Single Article View

Now we have been able to link together the index and show pages in our application using a simple a tag. What could be simpler than that?

Well, Rails has a method called link_to that can make that linking a little simpler. Let's use this in app/views/articles/index.html.erb:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, "/articles/#{article.id}" %>
    </li>
  <% end %>
</ul>

There we go, that is now a little bit cleaner. Rails has given us a way to shorten this code a little. But what you don't know yet is that this line can be made even simpler.

Rails has a feature called routing helpers. These are methods that can be used to generate route paths like "/articles/#{article.id}" programatically. We'll use one of these to generate the route for our article. To set this up, let's go back to config/routes.rb and change this line:

get "/articles/:id", to: "articles#show"

To this:

get "/articles/:id", to: "articles#show", as: :article

The :as option here tells Rails that we want routing helpers for this article route to be available in our application. Rails will then let us use this helper to build that route.

Let's look at how we can use that in app/views/articles/index.html.erb now, by changing the end of the link_to call to this:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article_path(article) %>
    </li>
  <% end %>
</ul>

The link_to now assembles its path using the article_path helper. This will still generate the same /articles/:id route we used earlier, but now it happens programatically instead. Now there is not so much switching happening between HTML and Ruby in this code. We enter Ruby, generate a link, and exit Ruby. The code still does the same thing: it links an article's title to the show page for that article.

TIP: To learn more about routing, read the Rails Routing from the Outside In Guide.

We now have an index action that lists the articles, and a show action that shows the title and body for a specific article. Before we move on, we'll make one more little change: we'll add a "Back" link in app/views/articles/show.html.erb:

<h1><%= @article.title %></h1>

<%= @article.body %>

<div>
  <%= link_to "Back", "/" %>
</div>

This will allow us to navigate back to the list of articles easily.

With that small change done, let's now look at how we can create new articles within this application.

Creating new articles

To have a place to create new articles in our application, we're going to need create a new route, action and view. On that view, we're going to have this form:

IMAGE GOES HERE

Let's start with the route:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/new", to: "articles#new", as: :new_article
  get "/articles/:id", to: "articles#show", as: :article
end

This place to create new articles will be /articles/new, and the route for this has very intentionally been placed above the route for the show action. The reason for this is because routes in a Rails application are matched top-to-bottom. If we had /articles/:id first, that route would match /articles/new, and so if we went to /articles/new, the show action would serve that request, not the new action. And so for this reason, we put the new route above the show action.

This /articles/new route will send the request to the new action within the ArticlesController, which we'll see in a minute. We've added the :as option here, as we will be using the new_article_path helper in a little while to provide a way to navigate to this form.

If we were to attempt to go to this route now, would see an error for the first time:

Unknown action new for ArticlesController!

This error indicates that Rails cannot find the new action inside the ArticlesController. No worries, we will need to define this action.

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
  end
end

We can put the new action under show in the controller, because the order of methods in classes doesn't matter in Ruby.

With the new method defined in ArticlesController, if you refresh http://localhost:3000/articles/new you'll see another error:

Template is missing for articles/new

You're getting this error now because Rails expects empty actions like this one to have views associated with them to display their information. With no view available, Rails will raise an exception.

Let's look at the full error message again:

ArticlesController#new is missing a template for request formats: text/html

NOTE! Unless told otherwise, Rails expects an action to render a template with the same name, contained in a folder named after its controller. If this controller is an API responding with 204 (No Content), which does not require a template, then this error will occur when trying to access it via browser, since we expect an HTML template to be rendered for such requests. If that's the case, carry on.

The message identifies which template is missing. In this case, it's the articles/new template. Next the message contains request.formats which specifies the format of template to be served in response. It is set to text/html as we requested this page via browser, so Rails is looking for an HTML template.

The simplest template that would work in this case would be one located at app/views/articles/new.html.erb. The extension of this file name is important: the first extension is the format of the template, and the second extension is the handler that will be used to render the template. Think of it reading right-to-left: "I'm going to execute ERB to generate HTML for the new action".

So let's now go ahead now and create a new file at app/views/articles/new.html.erb and write this content in it:

<h1>New Article</h1>

To create a form within this template, you will use a form builder. The primary form builder for Rails is provided by a helper method called form_with. To use this method, add this code into app/views/articles/new.html.erb:

<h1>New Article</h1>

<form action="/articles" method="post">
  <p>
    <label for="title">Title</label><br>
    <input type="text" id="title" name="title" />
  </p>

  <p>
    <label for="text">Text</label><br>
    <textarea name="text" id="text"></textarea>
  </p>

  <p>
    <input type="submit" value="Save Article" />
  </p>
</form>

This is an awful lot of typing for building a form. Fortunately, Rails provides helpers for us to simplify matters:

<h1>New Article</h1>

<%= form_with scope: :article, local: true do |form| %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>

  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>

  <p>
    <%= form.submit %>
  </p>
<% end %>

The form_with helper method allows us to build a form. The first line of this provides us a block argument called form, and then throughout the form we use that to build labels and text inputs for our field.

NOTE: By default form_with submits forms using Ajax thereby skipping full page redirects. To make this guide easier to get into we've disabled that with local: true for now.

This ERB code that uses form_with will output a HTML form that looks very similar to the one we hand-rolled, but there are some key differences. Here's what the form_with outputs:

<form action="/articles/new" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="DIwa34..." />
  <p>
    <label for="article_title">Title</label><br>
    <input type="text" name="article[title]" id="article_title" />
  </p>

  <p>
    <label for="article_text">Text</label><br>
    <textarea name="article[text]" id="article_text">
</textarea>
  </p>

  <p>
    <input type="submit" name="commit" value="Save Article" data-disable-with="Save Article" />
  </p>
</form>

The first key difference is that there is a hidden field called authenticity_token at the top. This is a security feature of Rails and it prevents outside people from submitting your forms maliciously using a technique called Cross Site Request Forgery. This Stack Overflow answer explains further.

The labels and fields are mostly the way they were, with a key difference: the name fields have an article[] wrapping around their values. This wrapping comes from the scope argument that we have passed to form_with. This wrapping groups all the fields of the form into one hash once they're submitted, and that will make it easy to process once they reach our application.

Speaking of, let's try and fill out this form now with a title and a body for our 3rd article:

The third article

There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the action attribute for the form is pointing at /articles/new. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new article.

The form needs to use a different URL in order to go somewhere else. This can be done quite simply with the :url option of form_with. Typically in Rails, the action that is used for new form submissions like this is called "create", and so the form should be pointed to that action.

Edit the form_with line inside app/views/articles/new.html.erb to look like this:

<%= form_with scope: :article, url: "/articles", local: true do |form| %>

Once the form is submitted, it will send a POST request to /articles. If we hit submit on that form now, we'll be shown a Routing Error:

Routing Error

This error means that we haven't set up a route to handle POST requests to /articles. If we look in our config/routes.rb file, we'll see that is correct:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/new", to: "articles#new", as: :new_article_path
  get "/articles/:id", to: "articles#show", as: :article
end

Let's add this new route now:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/new", to: "articles#new", as: :new_article_path
  get "/articles/:id", to: "articles#show", as: :article
  post "/articles", to: "articles#create"
end

TIP: The get and post methods that we use in config/routes.rb match HTTP request methods. These methods are conventions used across all HTTP applications -- not just Rails! -- to clearly indicate what sort of action we want to do. A GET request is one that retrieves information. A POST request is one that adds information. For more detials on these, see this MDN article: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods.

When Rails receives a POST /articles request, it will now route that request to the create action of the ArticlesController. However, if we re-submit that form, we'll see that the action cannot be found:

Unknown action create for ArticlesController

You now need to create the create action within the ArticlesController for this to work.

Creating Articles

To make the "Unknown action" go away, we can define a create action within the ArticlesController class in app/controllers/articles_controller.rb, underneath the new action, as shown:

class ArticlesController < ApplicationController
  def new
  end

  def create
  end
end

If you re-submit the form now, you may not see any change on the page. Don't worry!

This is because Rails by default returns 204 No Content response for an action if we don't specify what the response should be. We just added the create action but didn't specify anything about how the response should be. In this case, the create action should save our new article to the database.

When a form is submitted, the fields of the form are sent to Rails as parameters. Yes, there are the same parameters as we saw earlier when we used params[:id]. These parameters can then be referenced inside the controller actions, typically to perform a particular task. To see what these parameters look like, change the create action to this:

def create
  render plain: params[:article].inspect
end

The render method here is taking a very simple hash with a key of :plain and value of params[:article].inspect. The params method is the object which represents the parameters (or fields) coming in from the form. The params method returns an ActionController::Parameters object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. Thanks to the use of the scope option on the form, all of our form's parameters are grouped under params[:article].

TIP: Ensure you have a firm grasp of the params method, as you'll use it fairly regularly. Let's consider an example URL: http://www.example.com/?username=dhh&email=dhh@email.com. In this URL, params[:username] would equal "dhh" and params[:email] would equal "dhh@email.com".

If you re-submit the form one more time, you'll see something that looks like the following:

<ActionController::Parameters {"title"=>"Article the Third", "text"=>"The Trilogy Ends"} permitted: false>

This action is now displaying the parameters for the article that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them.

Let's change the create action to use the Article model to save the data in the database. Let's change the create action to look like this:

def create
  article = Article.new(params[:article])
  article.save

  redirect_to article_path(article)
end

NOTE: We're not using an instance variable in this action. This is because this action redirects at the end, and since there is a redirection there is no view. So there is no need to make these variables instance variables.

Here we use some familar code to create a new article -- we saw this previously right after we generated the Article model. The call to new and then to save will create a new article record in the database.

The final line, a redirect_to, uses article_path to redirect back to the show action.

If you now go to http://localhost:3000/articles/new you'll almost be able to create an article. Try it! You should get an error that looks like this:

Forbidden attributes for new article

Rails has several security features that help you write secure applications, and you're running into one of them now. This one is called strong parameters, which requires us to tell Rails exactly which parameters are allowed into our controller actions.

Why do you have to bother? The ability to grab and automatically assign all controller parameters to your model in one shot makes the programmer's job easier, but this convenience also allows malicious use. What if this form was a bank account and we allowed just anyone to add in a new field that set their balance to whatever they wished? This would end up bad for us!

We have to define our permitted controller parameters to prevent wrongful mass assignment. In this case, we want to both allow and require the title and body parameters for valid use of create. The syntax for this introduces require and permit. The change will involve one line in the create action:

  @article = Article.new(params.require(:article).permit(:title, :body))

This code is quite long and is often pulled out into its own method so it can be reused by multiple actions in the same controller. Above and beyond mass assignment issues, the method is often made private to make sure it can't be called outside its intended context. Here is the result:

def create
  @article = Article.new(article_params)

  @article.save
  redirect_to @article
end

private
  def article_params
    params.require(:article).permit(:title, :body)
  end

TIP: For more information, refer to the reference above and [this blog article about Strong Parameters] (https://weblog.rubyonrails.org/2012/3/21/strong-parameters/).

If we attempt to submit our form once more, this time it will succeed and we'll see the article's title and body. The URL should be http://localhost:3000/articles/3, indicating that we're now on the show action.

Before we wrap up this section, let's add a link to app/views/articles/index.html.erb so that we can go to the "New Article" page from there:

<h1>Articles</h1>

<%= link_to "New Article", new_article_path %>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article_path(article) %>
    </li>
  <% end %>
</ul>

Now we'll have an easy link to go back to that page:

Three articles

Great! That's another two actions finished in our controller: new and create.

Adding Some Validation

Sometimes, in web applications, we want to make sure certain fields are filled in.

The model file, app/models/article.rb is about as simple as it can get:

class Article < ApplicationRecord
end

There isn't much to this file - but note that the Article class inherits from ApplicationRecord. ApplicationRecord inherits from ActiveRecord::Base which supplies a great deal of functionality to your Rails models for free. We've used some of this already: Article.new, Article.all, Article.find and so on.

One of these pieces of functionality is that Active Record includes methods to help you validate the data that you send to models and it's easy to use.

Open the app/models/article.rb file and edit it to this:

class Article < ApplicationRecord
  validates :title, presence: true,
                    length: { minimum: 5 }
end

These changes will ensure that all articles have a title that is at least five characters long. Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects. Validations are covered in detail in Active Record Validations.

This validation will now only let us save articles that have titles longer than 5 characters. Let's open up the console now and try:

rails console
irb(main):001:0> invalid_article = Article.new
=> #<Article id: nil, title: nil, body: nil, created_at: nil, updated_at: nil>
irb(main):002:0> invalid_article.save
=> false

When save returns false, it means that the object is invalid and won't be saved to the database. To find out why, we can use this code:

irb(main):003:0> invalid_article.errors.full_messages
=> ["Title can't be blank", "Title is too short (minimum is 5 characters)"]

The errors.full_messages method chain shows two validation failure messages for our model:

  • The title can't be blank
  • The title is too short (minimum of 5 characters)

That's because we've left the title blank. Now let's see what happens when we save an article with a valid title:

irb(main):006:0> article = Article.new title: "Getting Started"
=> #<Article id: nil, title: "Getting Started", body: nil, created_at: nil, updated_at: nil>

irb(main):007:0> article.save
   (0.1ms)  begin transaction
  Article Create (0.4ms)  INSERT INTO "articles" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "Getting Started"], ["created_at", "2020-01-19 09:56:25.693465"], ["updated_at", "2020-01-19 09:56:25.693465"]]
   (0.6ms)  commit transaction
=> true

The save call here has returned true, indicating that the article has passed validations. Also in the console, we can see an Article Create message, that contains an INSERT INTO database query, and so we can be confident that this article has now been inserted into our database.

Now that we've seen how to handle invalid and valid articles in the console, let's try using this same technique in our controller.

If you open app/controllers/articles_controller.rb again, you'll notice that we don't check the result of calling @article.save inside the create action.

def create
  @article = Article.new(article_params)

  @article.save
  redirect_to @article
end

If @article.save fails in this situation, we need to do something different: we need to show the form again to the user so that they can correct their mistake.

To do this, let's change the create action to either redirect to the article if the save was successful (the method returns true) or to show the new form again if the save failed:

def new
  @article = Article.new
end

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to article_path(@article)
  else
    render 'new'
  end
end

The first thing to note here is that we've now switched from using a local variable article to using an instance variable, @article. The reason for this is the else statement. Inside that else we tell Rails to render 'new'. This tells Rails that we want the app/views/articles/new.html.erb view to be rendered in the case where our save fails.

If you reload http://localhost:3000/articles/new and try to save an article without a title, Rails will send you back to the form, but that's not very useful. It doesn't tell us why something went wrong. To do that, we'll need to modify app/views/articles/new.html.erb to check for error messages:

<h1>New Article</h1>

<%= form_with scope: :article, url: "/articles", local: true do |form| %>

  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>

  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>

  <p>
    <%= form.submit %>
  </p>

<% end %>

At the top of this view, we're now using @article.errors to check for any errors. The @article variable here will come from the create action, when the app/views/articles/new.html.erb view is rendered due to an invalid article.

Inside the check for any errors, we call pluralize. pluralize is a rails helper that takes a number and a string as its arguments. If the number is greater than one, the string will be automatically pluralized.

If we attempt to go to http://localhost:3000/articles/new at this point, we'll see it fail:

NoMethodError

This is happening because we're referring to a variable called @article within app/views/articles/new.html.erb, but the new action does not provide this variable at all.

The path to this error is:

  1. Browser goes to http://localhost:3000/articles/new
  2. Rails sees /articles/new is the route, routes the request to the ArticlesController's new action
  3. The new action is blank, and so Rails defaults to rendering app/views/articles/new.html.erb.
  4. The template attempts to reference @article, but it is not defined.

So to make this error go away, we need to define this @article variable. We can do it like this in the new action inside app/controllers/articles_controller.rb:

def new
  @article = Article.new
end

This @article is a brand-new Article object, and will be perfect for our form. It doesn't have any errors on it -- because we haven't tried saving it yet! -- and so the form will not display any errors.

If we refresh this http://localhost:3000/articles/new page, we should see our form renders once again.

The form works again

Now you'll get a nice error message when saving an article without a title when you attempt to do just that on the new article form http://localhost:3000/articles/new:

Form With Errors

And there we have it! We now have the ability to create new articles within our application.

Updating Articles

Now what should happen if we make a mistake when creating an article? Well, we should have a way to edit an article and correct that mistake.

Our edit form will look just like our new form, just with a few differences:

Edit form

Firstly, the title will say "Edit Article", not "New Article". Secondly, the fields will be filled out with the article's current values. And lastly, the submit button says "Update Article", not "Save Article".

To add this feature to our application, we're going to need to add a new route, a route just for editing articles. Let's do this now in config/routes.rb:

get "/articles/:id/edit", to: "articles#edit", as: :edit_article

This new route is another get route. This time, we're routing to /articles/:id/edit, so that we can see that edit form for a particular article. Which article we're editing depends on that :id parameter in the route.

The route will be handled by the edit action within ArticlesController. We'll add that action soon.

The as option will provide us with a routing helper that we can use across our application to take us to this edit form for a specific article.

As a first step, let's add an "Edit" link for each article on app/views/articles/index.html.erb:

<h1>Articles</h1>

<%= link_to "New Article", new_article_path %>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article_path(article) %>
      <%= link_to "Edit", edit_article_path(article) %>
    </li>
  <% end %>
</ul>

This "Edit" link will now appear next to all of the articles at http://localhost:3000/articles.

Articles with edit links

If we click on any one of those "Edit" links, we'll see that we haven't yet defined the edit action.

No edit action

So the next step here is to add that action to our controller. Let's open app/controllers/articles_controller.rb and add that action:

def new
  @article = Article.new
end

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

def edit
  @article = Article.find(params[:id])
end

NOTE: We're using edit to render just a form to display the current values of the article. For the actual updating of the article, we'll use a different action for this shortly called update.

The view will contain a form similar to the one we used when creating new articles. Create a file called app/views/articles/edit.html.erb and put this content inside:

<h1>Edit Article</h1>

<%= form_with model: @article, local: true do |form| %>

  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>

  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>

  <p>
    <%= form.submit %>
  </p>

<% end %>

<%= link_to 'Back', articles_path %>

The only things different in this view are the <h1> at the top of the view, and the form_with. The form_with is not using scope or url, but is instead using a different key called model.

The model key for form_with takes an instance of a model's class and builds a form for that particular object. By using form_with in this way, Rails will pre-populate the title and body fields in this form for us.

There's one extra feature that Rails does for us that is not immediately obvious from looking at the form itself. This feature requires us to look at the HTML source of this form. Inside this source at the top of the <form> element, here's what we'll see:

<form action="/articles/1" accept-charset="UTF-8" method="post">
<input type="hidden" name="_method" value="patch" />

Firstly, the action attribute for this form goes to a route called /articles/1. This path was automatically generated by Rails; in short: it uses the article_path helper to generate this route. The second thing to notice is that hidden field. This hidden field is a special field that will make the form do a PATCH request when this form is submitted, instead of the default POST (as is configured in the form element's method attribute).

This means that our form will make a PATCH /articles/1 request when it is submitted. If we hit submit on the form, we'll see that this is correct, and that this route is currently missing:

No route matches [PATCH] "/articles/1"

This route is supposed to handle the submission of our form, but the route does not exist yet. To make this form work, we'll need to define this route. Let's go back to config/routes.rb and define this route:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/new", to: "articles#new", as: :new_article
  get "/articles/:id", to: "articles#show", as: :article
  post "/articles", to: "articles#create"
  get "/articles/:id/edit", to: "articles#edit", as: :edit_article
  patch "/articles/:id", to: "articles#update"
end

This patch method will generate us a route for PATCH /articles/:id requests. We use the PATCH HTTP routing method for when we want to modify an existing resource.

These requests to PATCH /articles/:id will be routed to the update action in our ArticlesController. Let's add that action now underneath the edit action:

def edit
  @article = Article.find(params[:id])
end

def update
  @article = Article.find(params[:id])

  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

private
  def article_params
    params.require(:article).permit(:title, :body)
  end

The new method, update, is used when you want to update a record that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the article we want to show the form back to the user.

We reuse the article_params method that we defined earlier for the create action. We want to accept the same parameters here, and so it makes sense to use the same article_params method.

TIP: It is not necessary to pass all the attributes to update. For example, if @article.update(title: 'A new title') was called, Rails would only update the title attribute, leaving all other attributes untouched.

Let's try this again. We'll go to http://localhost:3000, click the "Edit" link next to one of the articles and change its title. I'm going to change the "Hello Rails" article's title to "Hello Rails, how are you today?". When this happens and we submit the form, we will see the new title for that article:

How are you, Rails?

On this page, we're currently missing a way to edit an article. This route that we're currently on is http://localhost:3000/articles/1, and we know that the route matches to app/views/articles/show.html.erb.

This now finishes our adventures in adding the ability to edit an article in this application.

Using partials to clean up duplication in views

Our edit page looks very similar to the new page; in fact, they both share almost same code for displaying the form. Rails has yet another great feature that we can use to reduce this duplication and this feature is called partials.

Partials allow us to extract out comon pieces of views into a file that is then shared across many different views, or in this case, just two views. Let's remove this duplication by using a partial. By convention, partial files are prefixed with an underscore.

TIP: You can read more about partials in the Layouts and Rendering in Rails guide.

Create a new file app/views/articles/_form.html.erb. We're going to copy most of app/views/articles/edit.html.erb into this new partial file:

<%= form_with model: article, local: true do |form| %>

  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>

  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>

  <p>
    <%= form.submit %>
  </p>

<% end %>

This partial can now be used in both the new and edit views. Let's update the new view:

<h1>New Article</h1>

<%= render 'form', article: @article %>

Then do the same for the app/views/articles/edit.html.erb view:

<h1>Edit Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Back', articles_path %>

This render call in a view works differently to the render call in a controller. Back in the create action for ArticlesController, we have this:

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

This render method call will render a view, not a partial. In this case, it will render the app/views/articles/new.html.erb view.

But when we call render inside a view, it will render a partial. When we call render 'form', article: @article inside our new.html.erb and edit.html.erb views, this will render the app/views/articles/_form.html.erb partial. How does Rails know that we want this particular form partial? It assumes we want the one in the same directory as the current view by default. This is another one of Rails' conventions at work!

The article: @article syntax at the end of this line tells Rails that we want to pass the instance variable @article to the partial as a local variable called article.

Inside that partial, we can access whatever the current article is by using the article local variable.

Now, an interesting thing happens here. When this partial is rendered for the new action, the form will submit to the create action. But when it's rendered for the edit action, it will submit to the update action. Go ahead and try it.

How can one piece of code do two things? The way this works lies in the magic of form_with and what it outputs, depending on its model option.

When this partial is rendered by the new action, the @article variable is set like this:

def new
  @article = Article.new
end

The form_with helper from Rails detects that this object hasn't yet been saved to the database, and therefore assumes we want to display a form for creating a new article. If you look at the HTML source from http://localhost:3000/articles/new, you'll see the form is configured like this:

<form action="/articles" accept-charset="UTF-8" method="post">

When the form is submitted, it will make a POST /articles request. This will go to the create action in ArticlesController, because that's how our routes are configured:

post "/articles", to: "articles#create"

Over in the edit action, we instead set @article like this:

def edit
  @article = Article.find(params[:id])
end

This @article represents an article that has already been saved to the database, and so form_with behaves different. Let's look at the HTML source from http://localhost:3000/articles/1/edit and see:

<form action="/articles/1" accept-charset="UTF-8" method="post"><input type="hidden" name="_method" value="patch" />

This is the same form_with method call that is running, but it is acting differently. This time, the form is generated with an action of /articles/1. The hidden field called _method will make Rails do a PATCH /articles/1 request. If we look in our routes file, we'll see that such a request goes to the update action:

patch "/articles/:id", to: "articles#update"

This is no coincidence. We have chosen these routes very specifically so that we can follow Rails conventions. The form_with helper acts differently depending on if the @article has been saved or not, and so we can use this one partial to represent a form in either new.html.erb or edit.html.erb.

Partials are a very handy feature of Rails that we can use to remove duplication between separate views. And combining them with form_with allows us to merge together two forms into one, without sacrificing any of our sanity.

Deleting Articles

We're now able to see, create, and update articles within our application. The final part that we'll cover for articles in this guide is how to delete them.

In order to edit articles, we provided a link right next to the article's title in app/views/articles/index.html.erb. To delete articles, let's do the same thing:

<h1>Articles</h1>

<%= link_to "New Article", new_article_path %>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article_path(article) %>
      <%= link_to "Edit", edit_article_path(article) %>
      <%= link_to "Delete", article_path(article) %>
    </li>
  <% end %>
</ul>

This link_to won't delete the article. It will instead take us to the show action in ArticlesController and show us the article itself. We need to add one extra thing to this link, which is a method option:

<%= link_to "Delete", article_path(article), method: :delete
%>

This will make the link make a DELETE /articles/:id request. The DELETE HTTP method is one that we use when we want to delete things.

We can now refresh this page and click one of these "Delete" links. This request currently won't work, because we don't have a DELETE /articles/:id route set up:

No delete route

Let's add this route to config/routes.rb:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/new", to: "articles#new", as: :new_article
  get "/articles/:id", to: "articles#show", as: :article
  post "/articles", to: "articles#create"
  get "/articles/:id/edit", to: "articles#edit", as: :edit_article
  patch "/articles/:id", to: "articles#update"
  delete "/articles/:id", to: "articles#destroy"
end

This route will now match DELETE /articles/:id requests and send them to the destroy action in ArticlesController. Let's add this action now in the ArticlesController:

def destroy
  article = Article.find(params[:id])
  article.destroy

  redirect_to articles_path
end

The complete ArticlesController in the app/controllers/articles_controller.rb file should now look like this:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  def destroy
    article = Article.find(params[:id])
    article.destroy

    redirect_to '/'
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

You can call destroy on model instances when you want to delete them from the database. Note that we don't need to add a view for this action since we're redirecting back to '/' -- the root of our application.

If we click "Delete" again on our list of articles, we'll each article disappear in turn.

We might want to be a little mindful here and ask users if they're really sure that they want to delete an article. Having a link so close to "Edit" like this is a recipe for disaster!

To prompt the user, we're going to change the "Delete" link in app/views/articles/index.html.erb to this:

<%= link_to "Delete",
  article_path(article),
  method: :delete,
  data: {
    confirm: "Are you sure you want to delete this article?"
  }
%>

This data option uses a feature of Rails called Unobtrusive JavaScript. By default, Rails applications come with a little bit of JavaScript for features like this.

TIP: Learn more about Unobtrusive JavaScript on Working With JavaScript in Rails guide.

When we refresh this page and click "Delete" once again, we'll see a new dialog box appear:

Confirm Dialog

If you press "Cancel" on this box, nothing will happen. The article will not be deleted. But if you press "OK", then the article will be deleted. Rails provides this option on links just for links like this "Delete" link. We want people to be really sure that they mean to delete articles before they actually do it!

That is the last of our actions in the ArticlesController. We now have ways to create, read, update and delete articles. This pattern is so common in Rails applications that it even has its own acronym: CRUD: Create, Read, Update and Delete. What we have built here is a CRUD interface for articles.

Routing for resources

So far, we have not had to write much code to make our application functional. But there's one extra thing that will massively reduce the lines of code you will write in the future, and that thing is called resource routing.

Rails has a convention that it follows when it comes to routing. When we list a collection of a resource, such as articles, that list is going to appear under the index action. When we want to see a single resource, such as a single article, that appears at the show action, and so on.

So far, we have been following this convention in Rails without drawing attention too much attention to it. In this section, we're going to draw a lot of attention to it. By following this routing convention, we can simplify the code within config/routes.rb drastically. That file currently contains this code:

Rails.application.routes.draw do
  root "articles#index"
  get "/articles", to: "articles#index"
  get "/articles/new", to: "articles#new", as: :new_article
  get "/articles/:id", to: "articles#show", as: :article
  post "/articles", to: "articles#create"
  get "/articles/:id/edit", to: "articles#edit", as: :edit_article
  patch "/articles/:id", to: "articles#update"
  delete "/articles/:id", to: "articles#destroy"
end

We've been able to define our routes using the root, get, post, patch and delete helpers. But Rails comes with one helper that we haven't seen yet, and that helper is called resources.

We can delete most of the code in this routes file and replace it with this method call:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles
end

This one line replaces all 7 of the routes that we had defined previously. This is one of the parts of Rails that people claim as the most "magical", and hopefully by defining all 7 routes manually first you will gain an appreciation for the elegance of resources here.

To see what this has done, we can run this command in the terminal:

rails routes --controller articles

Or:

rails routes -c articles

This command will show us all the routes for the ArticlesController:

      Prefix Verb   URI Pattern                  Controller#Action
        root GET    /                            articles#index
    articles GET    /articles(.:format)          articles#index
 new_article GET    /articles/new(.:format)      articles#new
     article GET    /articles/:id(.:format)      articles#show
             POST   /articles(.:format)          articles#create
edit_article GET    /articles/:id/edit(.:format) articles#edit
             PATCH  /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy

This command shows us four things:

  • Prefix: The routing helper prefix that can be used to generate this route. For example "article" means article_path can be used.
  • Verb: The HTTP Verb / method that is used to make this request.
  • URI pattern: the path of the route that is used to make this request.
  • Controller & Action: The controller & action that will serve this request.

From this output, we'll be able to tell that a GET request to /articles/new will go to the ArticlesController's new action.

These are all the same routes that we had previously, it's just that we're using one line to generate them now instead of 7.

TIP: In general, Rails encourages using resources objects instead of declaring routes manually. For more information about routing, see Rails Routing from the Outside In.

We have now completely finished building the first part of our application. If you've gotten this far, give yourself a pat on the back! And maybe a little break before you continue on this guide.

We're about two-thirds of the way through, and have just a few more features of Rails to show off.

Adding Comments

Let's expand this application a little further by adding the ability for users to leave comments on articles.

Generating a Model

To start with, we're going to generate a model for comments.

We're going to see the same generator that we used before when creating the Article model. This time we'll create a Comment model to hold a reference to an article. Run this command in your terminal:

$ bin/rails g model Comment commenter:string body:text article:references

This command will generate four files:

invoke  active_record
create    db/migrate/[timestamp]_create_comments.rb
create    app/models/comment.rb
invoke    test_unit
create      test/models/comment_test.rb
create      test/fixtures/comments.yml

First, let's take a look at app/models/comment.rb:

class Comment < ApplicationRecord
  belongs_to :article
end

This is very similar to the Article model that you saw earlier. The difference is the line belongs_to :article, which sets up an Active Record association. You'll learn a little about associations in the next section of this guide. This belongs_to was added to our model because we specific article:references when we generated this model.

The references type is a special type that define an association between the model that we're generating and another model. In this case, we're saying that every comment belongs to an article.

Let's look at the migration next:

class CreateComments < ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, null: false, foreign_key: true

      t.timestamps
    end
  end
end

The t.references line creates does a few things:

  1. It adds a field called article_id to the comments table
  2. A database index is added for that column. This will speed up retrieving comments for particular articles.
  3. The null: false option says that in no circumstance can this column be set to a NULL value.
  4. The foreign_key: true option says that this column is linked to the articles table, and that the column's values must be represented in the articles table, in the id column. There can be no comments without a related article to match.

Go ahead and run the migration:

$ bin/rails db:migrate

Rails is smart enough to only execute the migrations that have not already been run against the current database, so in this case you will just see:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

This migration will create our comments table. It will look like this in our database:

id commenter body article_id created_at updated_at
 

Associating Models

Active Record associations let you easily declare the relationship between two models. In the case of comments and articles, you could write out the relationships this way:

  • Each comment belongs to one article.
  • One article can have many comments.

In fact, this is very close to the syntax that Rails uses to declare this association. You've already seen the line of code inside the Comment model (app/models/comment.rb) that makes each comment belong to an Article:

class Comment < ApplicationRecord
  belongs_to :article
end

You'll need to edit app/models/article.rb to add the other side of the association:

class Article < ApplicationRecord
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

These two declarations enable a good bit of automatic behavior. Let's explore some of this behaviour by starting up a new console:

rails c

First, let's find an article:

irb(main):001:0> article = Article.first

Article Load (0.1ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]

=> #<Article id: 1, title: "Hello Rails", body: "I'm on Rails", created_at: "2020-01-20 05:22:45", updated_at: "2020-01-20 05:22:45">

Next up, let's create a new comment for this article by using the comments association method:

irb(main):002:0> article.comments.create(commenter: "DHH", body: "Welcome to Rails!")

Comment Create (0.4ms)  INSERT INTO "comments" ("commenter", "body", "article_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["commenter", "DHH"], ["body", "Welcome to Rails!"], ["article_id", 9], ["created_at", "2020-01-20 06:19:33.572961"], ["updated_at", "2020-01-20 06:19:33.572961"]]

=> #<Comment id: 1, commenter: "DHH", body: "Welcome to Rails!", article_id: 1, created_at: "2020-01-20 06:19:33", updated_at: "2020-01-20 06:19:33">

If you look at the list of attributes here, you'll see that both commenter and body are set the values that we passed in. We have come to expect this behaviour from Active Record: we give it attributes, it sets them on the object.

What we haven't seen before is what has happened to article_id here. That attribute has been automatically set to the ID of the article object. This is what links that Comment object back to the article.

To find an article's comments, we can do this:

irb(main):003:0> article.comments

Comment Load (0.9ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? LIMIT ?  [["article_id", 9], ["LIMIT", 11]]

=> #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, commenter: "DHH", body: "Welcome to Rails!", article_id: 9, created_at: "2020-01-20 06:19:33", updated_at: "2020-01-20 06:19:33">]>

This comments method on Article objects allows us to work with the comments for any given article.

Similarly, there is an article method on comments. Let's see that in action too:

irb(main):004:0> comment = Comment.first

Comment Load (0.2ms)  SELECT "comments".* FROM "comments" ORDER BY "comments"."id" ASC LIMIT ?  [["LIMIT", 1]]

=> #<Comment id: 1, commenter: "DHH", body: "Welcome to Rails!", article_id: 9, created_at: "2020-01-20 06:19:33", updated_at: "2020-01-20 06:19:33">


irb(main):005:0> comment.article

Article Load (0.2ms)  SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 9], ["LIMIT", 1]]

=> #<Article id: 9, title: "dsfsadfasdf", body: "asdfasdfasdf", created_at: "2020-01-20 05:22:45", updated_at: "2020-01-20 05:22:45">

This article method is granted to us by the belongs_to method call in the Comment model.

TIP: For more information on Active Record associations, see the Active Record Associations guide.

Displaying comments on articles

Now that we can create comments on articles, it would be really useful to display them somewhere. The most appropriate place to do that would be within the app/views/articles/show.html.erb view. Let's change this view now to display all of the comments:

<h1><%= @article.title %></h1>

<%= @article.body %>

<h2>Comments</h2>

<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<div>
  <%= link_to "Back", "/" %>
</div>

The new code that we've just added to this view will go through all of the article's comments and display the commenter and the comment that was made. When we go to http://localhost:3000/articles/1 now, we should see this comment appear:

Article with comments

Well, that was pretty straight forward! Rails has given us an easy way to list all of the article comments, by way of the has_many method in the Article model.

Adding a comment

Now that we have a way to see all of the current comments, let's add a form that lets us create additional comments. To start with, we're going to put this form in app/views/articles/show.html.erb, just below the comments we just added:


<% @article.comments.each do |comment| %>
  ...
<% end %>

<h2>Add a comment:</h2>
<%= form_with model: [@article, @article.comments.build], local: true do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

This form looks almost like the one in app/views/articles/_form.html.erb, but it has one key difference: the model key has been passed an array, instead of a single model instance. What will happen here is that the form will build what's called a nested route for the comment.

The second element in that array is @article.comments.build. This is a helper method that comes from has_many, that is essentially equivalent to this code:

Comment.new(article_id: @article.id)

We're going to be building a new comment for the purposes of saving it to the database, eventually.

If we refresh http://localhost:3000/articles/1, we'll see an error message which hints a little bit at this nested route:

NoMethodError: article_comments_path

The form_with helper here is attempting to use a routing helper called article_comments_path to generate the route for the form. This routing helper doesn't exist yet, and we'll create it in a moment. But first, let's talk about how Rails came to be wanting article_comments_path in the first place.

When we use form_with's :model option, it combines the class names of the resources we pass it into a routing helper. Back when we were doing form_with model: @article, it would see that the class name of @article was Article. Then, form_with would see if this object had been saved to the database before or not. If the object had not been saved, the form would use articles_path -- the plural version of the routing helper. If the object had been saved, it would use article_path -- the singular version of routing helper.

The same rule applies here. form_with's underlying code checks to see what @article is first. It's an Article that has been saved to the database, so the first part of the routing helper is article. Then it checks what @article.comments.build is. This object is a Comment that has not been saved to the database, so the helper's next component is comments. Then we're out of array elements, so form_with puts _path on the end. This is how we arrive at article_comments_path.

This is another one of those excellent Rails conventions you've heard about throughout this guide. And it's one of the more "magical" (or "confusing") aspects of Rails. So don't worry too much if you don't get it first pass.

In order to solve the issue here, we need to add the route that has that routing helper. This time, however, instead of writing seven routes one-at-a-time for comments, just like we did for articles, we're going to use resources again.

Open up the config/routes.rb file again, and edit it as follows:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles do
    resources :comments
  end
end

This creates comments as a nested resource within articles. This is another part of capturing the hierarchical relationship that exists between articles and comments. By nesting comments inside of articles like this, this will give us the article_comments_path helper that our form is expecting.

We can verify this by going into a terminal and running:

rails routes -c comments

And we'll see these routes:

              Prefix Verb   URI Pattern                                       Controller#Action
    article_comments GET    /articles/:article_id/comments(.:format)          comments#index
                     POST   /articles/:article_id/comments(.:format)          comments#create
 new_article_comment GET    /articles/:article_id/comments/new(.:format)      comments#new
edit_article_comment GET    /articles/:article_id/comments/:id/edit(.:format) comments#edit
     article_comment GET    /articles/:article_id/comments/:id(.:format)      comments#show
                     PATCH  /articles/:article_id/comments/:id(.:format)      comments#update
                     PUT    /articles/:article_id/comments/:id(.:format)      comments#update
                     DELETE /articles/:article_id/comments/:id(.:format)      comments#destroy

The article_comments routing helper is the first line. We can see from this routing helper that it will generate the following path:

/articles/:article_id/comments

We can see this in action if we go back to http://localhost:3000/articles/1 and inspect the page's HTML source again. We'll see the form has that route as its action attribute:

<form action="/articles/9/comments" accept-charset="UTF-8" method="post">

TIP: For more information on routing, see the Rails Routing guide.

When we fill out the comment form and click "Create Comment", we'll now see that the CommentsController is missing:

Comments Controller missing

Generating a Controller

To fix this issue, we will need to generate the CommentsController. Let's do that now:

$ bin/rails g controller comments

This creates four files and one empty directory:

File/Directory Purpose
app/controllers/comments_controller.rb The Comments controller
app/views/comments/ Views of the controller are stored here
test/controllers/comments_controller_test.rb The test for the controller
app/helpers/comments_helper.rb A view helper file
app/assets/stylesheets/comments.scss Cascading style sheet for the controller

If we attempt to submit the form again, we'll see that the create action is missing in this new controller:

Create action missing in CommentsController

Let's wire up the create in app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

You'll see a bit more complexity here than you did in the controller for articles. That's a side-effect of the nesting that you've set up. Each request for a comment has to keep track of the article to which the comment is attached, thus the initial call to the find method of the Article model to get the article in question.

But where did we get the idea for article_id from? Well, if we look at our route again with:

rails routes -c comments

Then we'll see:

              Prefix Verb   URI Pattern                                       Controller#Action
    article_comments GET    /articles/:article_id/comments

The colon before :article_id indicates that this part of the URL will be available as params[:article_id] in our controller. This is why we're using :article_id here, and not :id.

In addition, the code takes advantage of some of the methods available for an association. We use the create method on @article.comments to create and save the comment. This will automatically link the comment so that it belongs to that particular article, just as we saw earlier when we created a comment in the Rails console.

Once we have made the new comment, we send the user back to the original article using the article_path(@article) helper. As we have already seen, this calls the show action of the ArticlesController which in turn renders the show.html.erb template.

If we fill out the comment form again, we will see our comment appear.

Now you can add articles and comments to your blog and have them show up in the right places.

Article with Two Comments

Refactoring

Now that we have articles and comments working, take a look at the app/views/articles/show.html.erb template. It is getting long and awkward. We can use partials to clean this view up.

Rendering Partial Collections

First, we will make a comment partial to extract showing all the comments for the article. Create the file app/views/comments/_comment.html.erb and put the following into it:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

Then you can change app/views/articles/show.html.erb to look like the following:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<%= render @article.comments %>

...

This is the third style of render that we've seen throughout this guide.

The first was render in an action. That one will render a view by using code like render "new".

The second was render in a view, we saw it in app/views/articles/new.html.erb and app/views/articles/edit.html.erb. It was written as <%= render "form", article: @article %>, and that meant to render the partial at app/views/articles/_form.html.erb.

This third one is render with a collection of objects. Rails will inspect these objects and see what class they are, and then it will render a partial that matches the name of the class: comments/_comment.html.erb for this one. If we were to do the same sort of thing for articles, it would render articles/_article.html.erb.

Inside the comments/_comment.html.erb partial, we're able to refer to the local variable comment to refer to each comment, just like we did earlier with the each version of this code.

Rendering a Partial Form

Let's keep tidying up this app/views/articles/show.html.erb view. We can definitely move that comment form out too.

Let us also move that new comment section out to its own partial. To do this, we create a file app/views/comments/_form.html.erb containing:

<%= form_with model: [article, article.comments.build], local: true do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

Then you make the app/views/articles/show.html.erb look like the following:

...

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= render "comments/form", article: @article %>

...

This is the second version of the render method: it will render that partial at app/views/comments/_form.html.erb and pass through the instance variable of @article as a local variable called article to that partial.

Deleting Comments

Another important feature of a blog is being able to delete spam comments. To do this, we need to implement a link of some sort in the view and a destroy action in the CommentsController.

So first, let's add the delete link in the app/views/comments/_comment.html.erb partial:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

The second argument to link_to is new to us. Well, sort of. We've seen it just before in the form for comments, but that was with form_with, and this is with link_to. This syntax for link_to works exactly the same as it did back in form_with; it works to build a routing helper. The link_to helper checks to see what kind of object comment.article is. It's an Article object and that object exists in the database, so the first part of that helper is article. Then the second object is a Comment object, and so the second part is comment. This means that the routing helper used will be article_comment_path.

We could write this code out in a longer fashion if we wished:

<p>
  <%= link_to 'Destroy Comment', article_comment_path(comment.article, comment),
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

But Rails' routing conventions save us some time and keystrokes by allowing us to write [comment.article, comment] instead.

Clicking this new "Destroy Comment" link will fire off a DELETE /articles/:article_id/comments/:id to our CommentsController, which can then use this to find the comment we want to delete. Right now, the destroy action that matches that route is missing, and so we will see this if we attempt to delete a comment:

Destroy not found

So let's add a destroy action to our controller (app/controllers/comments_controller.rb):

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

The destroy action will find the article we are looking at, locate the comment within the @article.comments collection, and then remove it from the database and send us back to the show action for the article.

If we attempt to delete a comment again, this time it will disppaear.

Deleting Associated Objects

If you delete an article, its associated comments will also need to be deleted, otherwise we would see an ActiveRecord::InvalidForeignKey error happen:

Foreign key constraint

This error happens because the database will not allow comments to be without an associated article, due to this line in the db/migrate/[timestamp]_create_comments.rb migration:

t.references :article, null: false, foreign_key: true

The foreign_key option on this line, when set to true, says that the article_id column within the comments table must have a matching id value in the articles table. If a situation arises where this might happen, the database raises a foreign key constraint error, which is what we're seeing here.

To avoid this issue, we need to give our Article's comments association one extra option, called dependent. Let's change app/models/article.rb to this:

class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

Now when an article is deleted, all of its comments will be deleted too, and we will avoid having a foreign key constraint error happen.

Security

Basic Authentication

If you were to publish your blog online, anyone would be able to add, edit and delete articles or delete comments.

Rails provides a very simple HTTP authentication system that will work nicely in this situation.

In the ArticlesController we need to have a way to block access to the various actions if the person is not authenticated. Here we can use the Rails http_basic_authenticate_with method, which allows access to the requested action if that method allows it.

To use the authentication system, we specify it at the top of our ArticlesController in app/controllers/articles_controller.rb. In our case, we want the user to be authenticated on every action except index and show, so we write that:

class ArticlesController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # snippet for brevity

We also want to allow only authenticated users to delete comments, so in the CommentsController (app/controllers/comments_controller.rb) we write:

class CommentsController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

  def create
    @article = Article.find(params[:article_id])
    # ...
  end

  # snippet for brevity

Now if you try to create a new article, you will be greeted with a basic HTTP Authentication challenge:

Basic HTTP Authentication Challenge

Other authentication methods are available for Rails applications. Two popular authentication add-ons for Rails are the Devise rails engine and the Authlogic gem, along with a number of others.

Other Security Considerations

Security, especially in web applications, is a broad and detailed area. Security in your Rails application is covered in more depth in the Ruby on Rails Security Guide.

What's Next?

Now that you've seen your first Rails application, you should feel free to update it and experiment on your own.

Remember, you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources:

Configuration Gotchas

The easiest way to work with Rails is to store all external data as UTF-8. If you don't, Ruby libraries and Rails will often be able to convert your native data into UTF-8, but this doesn't always work reliably, so you're better off ensuring that all external data is UTF-8.

If you have made a mistake in this area, the most common symptom is a black diamond with a question mark inside appearing in the browser. Another common symptom is characters like "ü" appearing instead of "ü". Rails takes a number of internal steps to mitigate common causes of these problems that can be automatically detected and corrected. However, if you have external data that is not stored as UTF-8, it can occasionally result in these kinds of issues that cannot be automatically detected by Rails and corrected.

Two very common sources of data that are not UTF-8:

  • Your text editor: Most text editors (such as TextMate), default to saving files as UTF-8. If your text editor does not, this can result in special characters that you enter in your templates (such as é) to appear as a diamond with a question mark inside in the browser. This also applies to your i18n translation files. Most editors that do not already default to UTF-8 (such as some versions of Dreamweaver) offer a way to change the default to UTF-8. Do so.
  • Your database: Rails defaults to converting data from your database into UTF-8 at the boundary. However, if your database is not using UTF-8 internally, it may not be able to store all characters that your users enter. For instance, if your database is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese character, the data will be lost forever once it enters the database. If possible, use UTF-8 as the internal storage of your database.

Friday, 17. January 2020

Code With Jason

Where to start with introducing TDD to a new Rails app

A Code With Jason reader recently wrote me with the following question:

How do I introduce TDD into a new Rails app? Where do I start? I am deeply knowledgable on RSpec and use it a lot. I am not sure how to get started with testing in Rails. When should testing be introduced? (hopefully as soon as possible) What should be testing vs. what should I assume works because it was tested in some other way? For example, why test generated code?

There are a lot of good questions here. I’ll pick a couple and address them individually.

How do I introduce TDD into a new Rails app?

This depends on your experience level with Rails and with testing.

I personally happen to be very experienced with both Rails and testing. When I build a new Rails application, I always start writing tests from the very beginning of the project.

If you’re new to Rails AND new to testing, I wouldn’t recommend trying to learn both at the same time. That’s too much to learn at once. Instead, get comfortable with Rails first and then start learning testing.

What if you’re comfortable with Rails but not testing yet? How do you introduce testing to a new Rails app then?

I would suggest starting with testing from the very beginning. Adding tests to your application is never going to be easier than it will be at the very beginning. In fact, retroactively adding tests to an existing application is notoriously super hard.

The kinds of tests to start with

What kinds of tests should you add at the beginning? I would recommend starting with acceptance tests, also known to some as integration tests, end-to-end tests or, in RSpec terminology, feature specs. The reason I recommend starting with feature specs is that they’re conceptually easy to grasp. They’re a simulation of a human opening a browser and clicking on things and typing stuff.

You can start with a feature spec “hello world” where you just visit a static page and verify that it says “hello world”. Once you’re comfortable with that, you can add feature specs to all your CRUD operations. I have a step-by-step formula for writing feature specs that you can use too.

Once you’re somewhat comfortable with feature specs, you can move on to model specs.

Testing vs. TDD

I’ve been sloppy with my language so far. I’ve been talking about testing but the question was specifically about TDD. How do you introduce TDD into a new Rails app?

First let me say that I personally don’t do TDD 100% of the time. I maybe do it 60% of the time.

I mainly write two types of tests in Rails: feature specs and model specs. When I’m building a new feature, I often don’t know exactly what form the UI is going to take or what labels or IDs all the page elements are going to have. This makes it really hard for me to try to write a feature spec before I write any code. So, usually, I don’t bother.

Another reason I don’t always do TDD is that I often use scaffolds, and scaffolds and TDD are incompatible. With TDD you write the tests first and the code after. Since scaffolds give you all the code up front, it’s of course impossible to write the tests before the code because the code already exists.

So again, the only case when I do TDD is when I’m writing model code, and I would guess this is maybe 60% of the time.

How, then, should you start introducing TDD to a new Rails app? I personally start TDD’ing when I start writing my first model code. The model code is often pretty trivial for the first portion of a Rails app’s lifecycle, so it’s usually a while before I get into “serious TDD”.

What should I be testing vs. what should I assume works because it was tested in some other way? For example, why test generated code?

I’ll answer the second part of this question first.

Why test generated code?

I assume when we say “generated code” we’re mainly talking about scaffolding.

If you’re never ever going to modify the generated code there’s little value in writing tests for it. Scaffold-generated CRUD features pretty much always just work.

However, it’s virtually never the case that a scaffolded feature comes out exactly the way we want with no modification required. For anything but the most trivial models, a little tweaking of the scaffold-generated code is necessary in order to get what we need, and each tweak carries a risk of introducing a bug. So it’s not really generated code. Once a human starts messing with it, all bets are off.

That’s not even the main value of writing tests for generated code though. The main value of the tests covering generated code (which, again, is rarely 100% actual generated code) is that the tests help protect against regressions that may be introduced later. If you have tests, you can refactor freely and be reasonably confident that you’re not breaking anything.

Having said that, I don’t try to test all parts of the code generated by a scaffold. There are some things that are pointless to test.

What should I test and what should I assume works?

When I generate a scaffold, there are certain types of tests I put on the scaffold-generated code. I have a step-by-step process I use to write specific test scenarios.

I typically write three feature specs: a feature spec for creating a record using valid inputs, a feature spec for trying to create a record using invalid inputs, and a feature spec for updating a record. That’s enough to give me confidence that things aren’t horribly broken.

At the model level, I’ll typically add tests for validations (I am TDD’ing in this case) and then add the validations. Usually I only need a few presence validations and maybe a uniqueness validation. It’s not common that I’ll need anything fancier than that at that time.

There are certain other types of tests I’ve seen that I think are pointless. These include testing the presence of associations, testing that a model responds to certain methods, testing for the presence of callbacks, and testing for database columns and indexes. There’s no value in testing these things directly. These tests are tautological. What’s better is to write tests for the behaviors that these things (i.e. these associations, methods, etc.) enable.

Suggestions for further reading

The post Where to start with introducing TDD to a new Rails app appeared first on Code with Jason.

Thursday, 16. January 2020

Ruby Conferences 'n' Camps in 2020 - What's Upcoming?

Rails Camp West @ Diablo Lake, Washington, United States Announced

Conferences 'n' Camps

What's News? What's Upcoming in 2020?

Rails Camp West
Sep/1-4 (4d) Tue-Fri @ Diablo Lake, Washington, United States • (Updates)

See all Conferences 'n' Camps in 2020».


RubyConf Belarus (BY) @ Minsk, Belarus Announced

Conferences 'n' Camps

What's News? What's Upcoming in 2020?

RubyConf Belarus (BY)
Apr/18 (1d) Sat @ Minsk, Belarus • (Updates)

See all Conferences 'n' Camps in 2020».

Monday, 13. January 2020

Aaron Patterson @ Seattle, WA › United States

Guide to String Encoding in Ruby

Encoding issues don’t seem to happen frequently, but that is a blessing and a curse. It’s great not to fix them very frequently, but when you do need to fix them, lack of experience can leave you feeling lost.

This post is meant to be a sort of guide about what to do when you encounter different types of encoding errors in Ruby. First we’ll cover what an encoding object is, then we’ll look at common encoding exceptions and how to fix them.

What are String encodings in Ruby?

In Ruby, strings are a combination of an array of bytes, and an encoding object. We can access the encoding object on the string by calling encoding on the string object.

For example:

>> x = 'Hello World'
>> x.encoding
=> #<Encoding:UTF-8>

In my environment, the default encoding object associated with a string us the “UTF-8” encoding object. A graph of the object relationship looks something like this:

string points at encoding

Changing a String’s Encoding

We can change encoding by two different methods:

  • String#force_encoding
  • String#encode

The force_encoding method will mutate the string object and only change which encoding object the string points to. It does nothing to the bytes of the string, it merely changes the encoding object associated with the string. Here we can see that the return value of encoding changes after we call the force_encode method:

>> x = 'Hello World'
>> x.encoding
=> #<Encoding:UTF-8>
>> x.force_encoding "US-ASCII"
=> "Hello World"
>> x.encoding
=> #<Encoding:US-ASCII>

The encode method will create a new string based on the bytes of the old string and associate the encoding object with the new string.

Here we can see that the encoding of x remains the same, and calling encode returns a new string y which is associated with the new encoding:

>> x = 'Hello World'
>> x.encoding
=> #<Encoding:UTF-8>
>> y = x.encode("US-ASCII")
>> x.encoding
=> #<Encoding:UTF-8>
>> y.encoding
=> #<Encoding:US-ASCII>

Here is a visualization of the difference:

changing encoding

Calling force_encoding mutates the original string, where encode creates a new string with a different encoding. Translating a string from one encoding to another is probably the “normal” use of encodings. However, developers will rarely call the encode method because Ruby will typically handle any necessary translations automatically. It’s probably more common to call the force_encoding method, and that is because strings can be associated with the wrong encoding.

Strings Can Have the Wrong Encoding

Strings can be associated with the wrong encoding object, and that is the source of most if not all encoding related exceptions. Let’s look at an example:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.encoding
=> #<Encoding:UTF-8>
>> x.valid_encoding?
=> false

In this case, Ruby associated the string "Hello \x93\xfa\x96\x7b" with the default encoding UTF-8. However, many of the bytes in the string are not valid Unicode characters. We can check if the string is associated with a valid encoding object by calling valid_encoding? method. The valid_encoding? method will scan all bytes to see if they are valid for that particular encoding object.

So how do we fix this? The answer depends on the situation. We need to think about where the data came from and where the data is going. Let’s say we’ll display this string on a webpage, but we do not know the correct encoding for the string. In that case we probably want to make sure the string is valid UTF-8, but since we don’t know the correct encoding for the string, our only choice is to remove the bad bytes from the string.

We can remove the unknown bytes by using the scrub method:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.valid_encoding?
=> false
>> y = x.scrub
>> y
=> "Hello ���{"
>> y.encoding
=> #<Encoding:UTF-8>
>> y.valid_encoding?
=> true

The scrub method will return a new string associated with the encoding but with all of the invalid bytes replaced by a replacement character, the diamond question mark thing.

What if we do know the encoding of the source string? Actually the example above is using a string that’s encoding using Shift JIS. Let’s say we know the encoding, and we want to display the string on a webpage. In that case we tag the string by using force_encoding, and transcode to UTF-8:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.force_encoding "Shift_JIS"
=> "Hello \x{93FA}\x{967B}"
>> x.valid_encoding?
=> true
>> x.encode "UTF-8" # display as UTF-8
=> "Hello 日本"

The most important thing to think about when dealing with encoding issues is “where did this data come from?” and “what will we do with this data?” Answering those two questions will drive all decisions about which encoding to use with which string.

Encoding Depends on the Context

Before we look at some common errors and their remediation, let’s look at one more example of the encoding context dependency. In this example, we’ll use some user input as a cache key, but we’ll also display the user input on a webpage. We’re going to use our source data (the user input) in two places: as a cache key, and something to display on a web page.

Here’s the code:

require "digest/md5"
require "cgi"

# Make a checksum
def make_checksum string
  Digest::MD5.hexdigest string
end

# Not good HTML escaping (don't use this)
# Returns a string with UTF-8 compatible encoding for display on a webpage
def display_on_web string
  string.gsub(/>/, "&gt;")
end

# User input from an unknown source
x = "Hello \x93\xfa\x96\x7b"
p ENCODING: x.encoding
p VALID_ENCODING: x.valid_encoding?

p display_on_web x
p make_checksum x

If we run this code, we’ll get an exception:

$ ruby thing.rb
{:ENCODING=>#<Encoding:UTF-8>}
{:VALID_ENCODING=>false}
Traceback (most recent call last):
        2: from thing.rb:20:in `<main>'
        1: from thing.rb:12:in `display_on_web'
thing.rb:12:in `gsub': invalid byte sequence in UTF-8 (ArgumentError)

The problem is that we have a string of unknown input with bytes that are not valid UTF-8 characters. We know we want to display this string on a UTF-8 encoded webpage, so lets scrub the string:

require "digest/md5"
require "cgi"

# Make a checksum
def make_checksum string
  Digest::MD5.hexdigest string
end

# Not good HTML escaping (don't use this)
# Returns a string with UTF-8 compatible encoding for display on a webpage
def display_on_web string
  string.gsub(/>/, "&gt;")
end

# User input from an unknown source
x = "Hello \x93\xfa\x96\x7b".scrub
p ENCODING: x.encoding
p VALID_ENCODING: x.valid_encoding?

p display_on_web x
p make_checksum x

Now when we run the program, the output is like this:

$ ruby thing.rb
{:ENCODING=>#<Encoding:UTF-8>}
{:VALID_ENCODING=>true}
"Hello ���{"
"4dab6f63b4d3ae3279345c9df31091eb"

Great! We’ve build some HTML and generated a checksum. Unfortunately there is a bug in this code (of course the mere fact that we’ve written code means there’s a bug! lol) Let’s introduce a second user input string with slightly different bytes than the first input string:

require "digest/md5"
require "cgi"

# Make a checksum
def make_checksum string
  Digest::MD5.hexdigest string
end

# Not good HTML escaping (don't use this)
# Returns a string with UTF-8 compatible encoding for display on a webpage
def display_on_web string
  string.gsub(/>/, "&gt;")
end

# User input from an unknown source
x = "Hello \x93\xfa\x96\x7b".scrub
p ENCODING: x.encoding
p VALID_ENCODING: x.valid_encoding?

p display_on_web x
p make_checksum x

# Second user input from an unknown source with slightly different bytes
y = "Hello \x94\xfa\x97\x7b".scrub
p ENCODING: y.encoding
p VALID_ENCODING: y.valid_encoding?

p display_on_web y
p make_checksum y

Here is the output from the program:

$ ruby thing.rb
{:ENCODING=>#<Encoding:UTF-8>}
{:VALID_ENCODING=>true}
"Hello ���{"
"4dab6f63b4d3ae3279345c9df31091eb"
{:ENCODING=>#<Encoding:UTF-8>}
{:VALID_ENCODING=>true}
"Hello ���{"
"4dab6f63b4d3ae3279345c9df31091eb"

The program works in the sense that there is no exception. But both user input strings have the same checksum despite the fact that the original strings clearly have different bytes! So what is the correct fix for this program? Again, we need to think about the source of the data (where did it come from), as well as what we will do with it (where it is going). In this case we have one source, from a user, and the user provided us with no encoding information. In other words, the encoding information of the source data is unknown, so we can only treat it as a sequence of bytes. We have two output cases, one is a UTF-8 HTML the other output is the input to our checksum function. The HTML requires that our string be UTF-8 so making the string valid UTF-8, in other words “scrubbing” it, before displaying makes sense. However, our checksum function requires seeing the original bytes of the string. Since the checksum is only concerned with the bytes in the string, any encoding including an invalid encoding will work. It’s nice to make sure all our strings have valid encodings though, so we’ll fix this example such that everything has a valid encoding.

require "digest/md5"
require "cgi"

# Make a checksum
def make_checksum string
  Digest::MD5.hexdigest string
end

# Not good HTML escaping (don't use this)
# Returns a string with UTF-8 compatible encoding for display on a webpage
def display_on_web string
  string.gsub(/>/, "&gt;")
end

# User input from an unknown source
x = "Hello \x93\xfa\x96\x7b".b
p ENCODING: x.encoding
p VALID_ENCODING: x.valid_encoding?

p display_on_web x.encode("UTF-8", undef: :replace)
p make_checksum x

# Second user input from an unknown source with slightly different bytes
y = "Hello \x94\xfa\x97\x7b".b
p ENCODING: y.encoding
p VALID_ENCODING: y.valid_encoding?

p display_on_web y.encode("UTF-8", undef: :replace)
p make_checksum y

Here is the output of the program:

$ ruby thing.rb
{:ENCODING=>#<Encoding:ASCII-8BIT>}
{:VALID_ENCODING=>true}
"Hello ���{"
"96cf6db2750fd4d2488fac57d8e4d45a"
{:ENCODING=>#<Encoding:ASCII-8BIT>}
{:VALID_ENCODING=>true}
"Hello ���{"
"b92854c0db4f2c2c20eff349a9a8e3a0"

To fix our program, we’ve changed a couple things. First we tagged the string of unknown encoding as “binary” by using the .b method. The .b method returns a new string that is associated with the ASCII-8BIT encoding. The name ASCII-8BIT is somewhat confusing because it has the word “ASCII” in it. It’s better to think of this encoding as either “unknown” or “binary data”. Unknown meaning we have some data that may have a valid encoding, but we don’t know what it is. Or binary data, as in the bytes read from a JPEG file or some such binary format. Anyway, we pass the binary string in to the checksum function because the checksum only cares about the bytes in the string, not about the encoding.

The second change we made is to call encode with the encoding we want (UTF-8) along with undef: :replace meaning that any time Ruby encounters bytes it doesn’t know how to convert to the target encoding, it will replace them with the replacement character (the diamond question thing).

SIDE NOTE: This is probably not important, but it is fun! We can specify what Ruby uses for replacing unknown bytes. Here’s an example:

>> x = "Hello \x94\xfa\x97\x7b".b
>> x.encoding
=> #<Encoding:ASCII-8BIT>
>> x.encode("UTF-8", undef: :replace, replace: "Aaron")
=> "Hello AaronAaronAaron{"
>> x.encode("UTF-8", undef: :replace, replace: "🤣")
=> "Hello 🤣🤣🤣{"
>> [_.encoding, _.valid_encoding?]
=> [#<Encoding:UTF-8>, true]

Now lets take a look at some common encoding errors in Ruby and what to do about them.

Encoding::InvalidByteSequenceError

This exception occurs when Ruby needs to examine the bytes in a string and the bytes do not match the encoding. Here is an example of this exception:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.encode "UTF-16"
Traceback (most recent call last):
        5: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `<main>'
        4: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `load'
        3: from /Users/aaron/.rbenv/versions/ruby-trunk/lib/ruby/gems/2.7.0/gems/irb-1.2.0/exe/irb:11:in `<top (required)>'
        2: from (irb):4
        1: from (irb):4:in `encode'
Encoding::InvalidByteSequenceError ("\x93" on UTF-8)
>> x.encoding
=> #<Encoding:UTF-8>
>> x.valid_encoding?
=> false

The string x contains bytes that aren’t valid UTF-8, yet it is associated with the UTF-8 encoding object. When we try to convert x to UTF-16, an exception occurs.

How to fix Encoding::InvalidByteSequenceError

Like most encoding issues, our string x is tagged with the wrong encoding. The way to fix this issue is to tag the string with the correct encoding. But what is the correct encoding? To figure out the correct encoding, you need to know where the string came from. For example if the string came from a Mime attachment, the Mime attachment should specify the encoding (or the RFC will tell you).

In this case, the string is a valid Shift JIS string, but I know that because I looked up the bytes and manually entered them. So we’ll tag this as Shift JIS, and the exception goes away:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.force_encoding "Shift_JIS"
=> "Hello \x{93FA}\x{967B}"
>> x.encode "UTF-16"
=> "\uFEFFHello \u65E5\u672C"
>> x.encoding
=> #<Encoding:Shift_JIS>
>> x.valid_encoding?
=> true

If you don’t know the source of the string, an alternative solution is to tag as UTF-8 and then scrub the bytes:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.force_encoding "UTF-8"
=> "Hello \x93\xFA\x96{"
>> x.scrub!
=> "Hello ���{"
>> x.encode "UTF-16"
=> "\uFEFFHello \uFFFD\uFFFD\uFFFD{"
>> x.encoding
=> #<Encoding:UTF-8>
>> x.valid_encoding?
=> true

Of course this works, but it means that you’ve lost data. The best solution is to figure out what the encoding of the string should be depending on its source and tag it with the correct encoding.

Encoding::UndefinedConversionError

This exception occurs when a string of one encoding can’t be converted to another encoding.

Here is an example:

>> x = "四\u2160"
>> x
=> "四Ⅰ"
>> x.encoding
=> #<Encoding:UTF-8>
>> x.valid_encoding?
=> true
>> x.encode "Shift_JIS"
Traceback (most recent call last):
        5: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `<main>'
        4: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `load'
        3: from /Users/aaron/.rbenv/versions/ruby-trunk/lib/ruby/gems/2.7.0/gems/irb-1.2.0/exe/irb:11:in `<top (required)>'
        2: from (irb):23
        1: from (irb):23:in `encode'
Encoding::UndefinedConversionError (U+2160 from UTF-8 to Shift_JIS)

In this example, we have two characters: “四”, and the Roman numeral 1 (“Ⅰ”). Unicode Roman numeral 1 cannot be converted to Shift JIS because there are two codepoints that represent that character in Shift JIS. This means the conversion is ambiguous, so Ruby will raise an exception.

How to fix Encoding::UndefinedConversionError

Our original string is correctly tagged as UTF-8, but we need to convert to Shift JIS. In this case we’ll use a replacement character when converting to Shift JIS:

>> x = "四\u2160"
>> y = x.encode("Shift_JIS", undef: :replace)
>> y
=> "\x{8E6C}?"
>> y.encoding
=> #<Encoding:Shift_JIS>
>> y.valid_encoding?
=> true
>> y.encode "UTF-8"
=> "四?"

We were able to convert to Shift JIS, but we did lose some data.

ArgumentError

When a string contains invalid bytes, sometimes Ruby will raise an ArgumentError exception:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.downcase
Traceback (most recent call last):
        5: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `<main>'
        4: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `load'
        3: from /Users/aaron/.rbenv/versions/ruby-trunk/lib/ruby/gems/2.7.0/gems/irb-1.2.0/exe/irb:11:in `<top (required)>'
        2: from (irb):34
        1: from (irb):34:in `downcase'
ArgumentError (input string invalid)
>> x.gsub(/ello/, "i")
Traceback (most recent call last):
        6: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `<main>'
        5: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `load'
        4: from /Users/aaron/.rbenv/versions/ruby-trunk/lib/ruby/gems/2.7.0/gems/irb-1.2.0/exe/irb:11:in `<top (required)>'
        3: from (irb):34
        2: from (irb):35:in `rescue in irb_binding'
        1: from (irb):35:in `gsub'
ArgumentError (invalid byte sequence in UTF-8)

Again we use our incorrectly tagged Shift JIS string. Calling downcase or gsub both result in an ArgumentError. I personally think these exceptions are not great. We didn’t pass anything to downcase, so why is it an ArgumentError? There is nothing wrong with the arguments we passed to gsub, so why is it an ArgumentError? Why does one say “input string invalid” where the other gives us a slightly more helpful exception of “invalid byte sequence in UTF-8”? I think these should both result in Encoding::InvalidByteSequenceError exceptions, as it’s a problem with the encoding, not the arguments.

Regardless, these errors both stem from the fact that the Shift JIS string is incorrectly tagged as UTF-8.

Fixing ArgumentError

Fixing this issue is just like fixing Encoding::InvalidByteSequenceError. We need to figure out the correct encoding of the source string, then tag the source string with that encoding. If the encoding of the source string is truly unknown, scrub it.

>> x = "Hello \x93\xfa\x96\x7b"
>> x.force_encoding "Shift_JIS"
=> "Hello \x{93FA}\x{967B}"
>> x.downcase
=> "hello \x{93FA}\x{967B}"
>> x.gsub(/ello/, "i")
=> "Hi \x{93FA}\x{967B}"

Encoding::CompatibilityError

This exception occurs when we try to combine strings of two different encodings and those encodings are incompatible. For example:

>> x = "四\u2160"
>> y = "Hello \x93\xfa\x96\x7b".force_encoding("Shift_JIS")
>> [x.encoding, x.valid_encoding?]
=> [#<Encoding:UTF-8>, true]
>> [y.encoding, y.valid_encoding?]
=> [#<Encoding:Shift_JIS>, true]
>> x + y
Traceback (most recent call last):
        5: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `<main>'
        4: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `load'
        3: from /Users/aaron/.rbenv/versions/ruby-trunk/lib/ruby/gems/2.7.0/gems/irb-1.2.0/exe/irb:11:in `<top (required)>'
        2: from (irb):50
        1: from (irb):50:in `+'
Encoding::CompatibilityError (incompatible character encodings: UTF-8 and Shift_JIS)

In this example we have a valid UTF-8 string and a valid Shift JIS string. However, these two encodings are not compatible, so we get an exception when combining.

Fixing Encoding::CompatibilityError

To fix this exception, we need to manually convert one string to a new string that has a compatible encoding. In the case above, we can choose whether we want the output string to be UTF-8 or Shift JIS, and then call encode on the appropriate string.

In the case we want UTF-8 output, we can do this:

>> x = "四"
>> y = "Hello \x93\xfa\x96\x7b".force_encoding("Shift_JIS")
>> x + y.encode("UTF-8")
=> "四Hello 日本"
>> [_.encoding, _.valid_encoding?]
=> [#<Encoding:UTF-8>, true]

If we wanted Shift JIS, we could do this:

>> x = "四"
>> y = "Hello \x93\xfa\x96\x7b".force_encoding("Shift_JIS")
>> x.encode("Shift_JIS") + y
=> "\x{8E6C}Hello \x{93FA}\x{967B}"
>> [_.encoding, _.valid_encoding?]
=> [#<Encoding:Shift_JIS>, true]

Another possible solution is to scrub bytes and concatenate, but again that results in data loss.

What is a compatible encoding?

If there are incompatible encodings, there must be compatible encodings too (at least I would think that). Here is an example of compatible encodings:

>> x = "Hello World!".force_encoding "US-ASCII"
>> [x.encoding, x.valid_encoding?]
=> [#<Encoding:US-ASCII>, true]
>> y = "こんにちは"
>> [y.encoding, y.valid_encoding?]
=> [#<Encoding:UTF-8>, true]
>> y + x
=> "こんにちはHello World!"
>> [_.encoding, _.valid_encoding?]
=> [#<Encoding:UTF-8>, true]
>> x + y
=> "Hello World!こんにちは"
>> [_.encoding, _.valid_encoding?]
=> [#<Encoding:UTF-8>, true]

The x string is encoded with “US ASCII” encoding and the y string UTF-8. US ASCII is fully compatible with UTF-8, so even though these two strings have different encoding, concatenation works fine.

String literals may default to UTF-8, but some functions will return US ASCII encoded strings. For example:

>> require "digest/md5"
=> true
>> Digest::MD5.hexdigest("foo").encoding
=> #<Encoding:US-ASCII>

A hexdigest will only ever contain ASCII characters, so the implementation tags the returned string as US-ASCII.

Encoding Gotchas

Let’s look at a couple encoding gotcha’s.

Infectious Invalid Encodings

When a string is incorrectly tagged, Ruby will typically only raise an exception when it needs to actually examine the bytes. Here is an example:

>> x = "Hello \x93\xfa\x96\x7b"
>> x.encoding
=> #<Encoding:UTF-8>
>> x.valid_encoding?
=> false
>> x + "ほげ"
=> "Hello \x93\xFA\x96{ほげ"
>> y = _
>> y
=> "Hello \x93\xFA\x96{ほげ"
>> [y.encoding, y.valid_encoding?]
=> [#<Encoding:UTF-8>, false]

Again we have the incorrectly tagged Shift JIS string. We’re able to append a correctly tagged UTF-8 string and no exception is raised. Why is that? Ruby assumes that if both strings have the same encoding, there is no reason to validate the bytes in either string so it will just append them. That means we can have an incorrectly tagged string “infect” what would otherwise be correctly tagged UTF-8 strings. Say we have some code like this:

def append string
  string + "ほげ"
end

p append("ほげ").valid_encoding? # => true
p append("Hello \x93\xfa\x96\x7b").valid_encoding? # = false

When debugging this code, we may be tempted to think the problem is in the append method. But actually the issue is with the caller. The caller is passing in incorrectly tagged strings, and unfortunately we might not get an exception until the return value of append is used somewhere far away.

ASCII-8BIT is Special

Sometimes ASCII-8BIT is considered to be a “compatible” encoding and sometimes it isn’t. Here is an example:

>> x = "\x93\xfa\x96\x7b".b
>> x.encoding
=> #<Encoding:ASCII-8BIT>
>> y = "ほげ"
>> y + x
Traceback (most recent call last):
        5: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `<main>'
        4: from /Users/aaron/.rbenv/versions/ruby-trunk/bin/irb:23:in `load'
        3: from /Users/aaron/.rbenv/versions/ruby-trunk/lib/ruby/gems/2.7.0/gems/irb-1.2.0/exe/irb:11:in `<top (required)>'
        2: from (irb):89
        1: from (irb):89:in `+'
Encoding::CompatibilityError (incompatible character encodings: UTF-8 and ASCII-8BIT)

Here we have a binary string stored in x. Maybe it came from a JPEG file or something (it didn’t, I just typed it in!) When we try to concatenate the binary string with the UTF-8 string, we get an exception. But this may actually be an exception we want! It doesn’t make sense to be concatenating some JPEG data with an actual string we want to view, so it’s good we got an exception here.

Now here is the same code, but with the contents of x changed somewhat:

>> x = "Hello World".b
>> x.encoding
=> #<Encoding:ASCII-8BIT>
>> y = "ほげ"
>> y + x
=> "ほげHello World"

We have the same code with the same encodings at play. The only thing that changed is the actual contents of the x string.

When Ruby concatenates ASCII-8BIT strings, it will examine the contents of that string. If all bytes in the string are ASCII characters, it will treat it as a US-ASCII string and consider it to be “compatible”. If the string contains non-ASCII characters, it will consider it to be incompatible.

This means that if you had read some data from your JPEG, and that data happened to all be ASCII characters, you would not get an exception even though maybe you really wanted one.

In my personal opinion, concatenating an ASCII-8BIT string with anything besides another ASCII-8BIT string should be an exception.

Anyway, this is all I feel like writing today. I hope you have a good day, and remember to check your encodings!

Sunday, 12. January 2020

On the Edge of Ruby

A Migration Path to Bundler 2+

Bundler 2 did not arrive quietly. It was noticed by almost every CI build failing when running bundle install. As a result, it seems many still avoid Bundler 2 and just use Bundler 1. In this post, I present some ideas on how to get more people to use Bundler 2, and no longer need Bundler 1 which will not be maintained forever.

The RubyGems Requirement of Bundler 2

The original release of Bundler 2, which is version 2.0.0, required such a recent RubyGems version that none of the released Ruby versions had a recent enough RubyGems version shipped with them. Bundler 2 also requires Ruby 2.3+, and finally drops support for Ruby 1.8.

This RubyGems version requirement was quickly found as problematic, and as a result Bundler 2.0.1 was released to lower the RubyGems version requirement to RubyGems 2.5. All versions of Ruby supported by Bundler 2 (that is, Ruby 2.3+) ship with RubyGems 2.5 or newer, but the blog post did not make this clear.

This resulted in a lot of confusion. Most repositories ended up explicitly updating RubyGems in CI to make Bundler 2.0.0 work, as mentioned in the original blog post.

To summarize, it is not needed to update RubyGems to use Bundler 2.0.1+.

In fact, I would even argue against updating RubyGems in CI because:

  • It makes the CI slower.
  • It makes the CI less reliable by frequently changing the RubyGems version used.
  • It uses the latest RubyGems instead of the well known version bundled with that Ruby version.

Bundler Version Autoswitching

Bundler 2 came with another feature, which to say the least is very confusing. This feature is actually implemented directly in RubyGems and it unfortunately included a bug, which led to even more confusion.

Supposing you have both Bundler 1.17.2 and Bundler 2.0.2 installed, what should this print?

$ bundle --version

For every other gem, the answer would be the latest version of the gem installed, so in this case 2.0.2.

The actual answer though, due to Bundler version autoswitching is “it depends on many things”. Specifically, if the current directory or any parent directory has a Gemfile.lock file with a BUNDLED WITH section, then that exact version, or the closest version with the same major version (depending on the RubyGems version), will be used. If such a version is not available, it fails with Could not find 'bundler' (<VERSION>) (e.g., when only Bundler 2 is installed with a Gemfile.lock BUNDLED WITH 1.17.2).

Otherwise, if there is no Gemfile.lock in the current directory or any parent directory, bundle behaves like a normal gem and uses the latest version.

As an example, this feature means that as long as there is a Gemfile.lock BUNDLED WITH 1.x, even if Bundler 2 is installed, Bundler 1 would be used, or the error above would be shown.

I think this “feature” is so confusing that it should be considered a bug. It’s counter-intuitive and prevents using Bundler 2 on Gemfile.lock BUNDLED WITH 1.x.

The Migration Problem

The problem I see with all this is I think very few people use Bundler 2, and rather they just keep using Bundler 1. I tried to estimate how often Bundler 2 is used by various means:

  • I made a poll on Twitter but that got rather few responses, with rather surprising results.
  • Some usage statistics from RubyGems.org show that Bundler 2 is only used in about 2% of requests to RubyGems.org.
  • TravisCI seems to currently use Bundler 1 for every Ruby version.
  • Most Ruby projects I have seen with a Gemfile.lock are BUNDLED WITH 1.x, showing Bundler 1 is used.

However, Bundler 1 seems no longer maintained (which is fair enough, who would want to maintain Ruby 1.8 compatibility?) and likely will no longer receive bug fixes and security patches.

So it is important that Rubyists migrate to Bundler 2, but I think the current situation made it so inconvenient that very few did. To address those concerns, I propose some ideas below to make migration to Bundler 2 easier.

A Better Migration Path for Bundler 2+

Here are my suggestions to make migration to Bundler 2 easier. I think it would be good to integrate them in future Bundler releases, including 2.x releases.

  1. Do not require a RubyGems version newer than what Ruby versions supported by Bundler ship. Requiring to update RubyGems causes many troubles as seen wth Bundler 2.0.0.
  2. Drop the version autoswitching mechanism in RubyGems for Bundler entirely. For older RubyGems versions with autoswitching, do whatever is necessary in Bundler to avoid that behavior, or at least warn.
  3. Every future version of Bundler should support previous versions of Gemfile and Gemfile.lock.
  4. Do not modify the Gemfile.lock if there would be no other change than the BUNDLED WITH version (e.g., for bundle install with an existing up-to-date Gemfile.lock).

This third point about backward compatibility might require extra maintenance work in Bundler. However, I think almost every software of the scale of Bundler needs to do this kind of things, and I would expect keeping a few extra parsers for older formats of Gemfile.lock is not so much work. For instance, even in Bundler 10, I expect installing gems from an already-resolved Gemfile.lock is basically just installing every listed gem with the listed version. Why Gemfile and not just Gemfile.lock? Because Bundler evaluates the Gemfile in all cases in its current design.

I think supporting older Gemfile and Gemfile.lock is the key for more people to adopt newer versions of Bundler. And importantly, this is the only way people can use newer versions of Bundler and stop using unmaintained and old Bundler versions for existing Gemfile.lock files with BUNDLED WITH 1.x (not all Gemfile.lock will be migrated immediately to Bundler 2).

Conclusion

I shared these suggestions with the Bundler team in October 2019 and they generally agreed and already discussed these subjects at RubyKaigi 2019.

Some of the suggestions listed above have already been addressed. For instance, the RubyGems version requirement (#1) was fixed in 2.0.1, one day after the 2.0.0 release.

The latest RubyGems and Bundler 2 can actually already install a Gemfile.lock BUNDLED WITH 1.x (#3), but it unfortunately changes the BUNDLED WITH section for seemingly no reason (#4 not yet). This can be seen on 2.7.0-preview2:

$ chruby 2.7.0-preview2
$ bundle _2.1.0.pre.2_ install
...
Warning: the lockfile is being updated to Bundler 2,
after which you will be unable to return to Bundler 1.
Bundle complete! 2 Gemfile dependencies, 7 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
$ git diff
--- a/Gemfile.lock
+++ b/Gemfile.lock
 BUNDLED WITH
-   1.17.2
+   2.1.0.pre.2

That changes is annoying. Suppose I’m contributing to Rails and I’m just doing bundle install (without modifying any Gemfile). If this changes the Gemfile.lock I will have to constantly undo that change, or be careful to not commit it and keep a dirty working tree. If it is committed and merged, it would force every single contributor of Rails (and the CI) to use Bundler 2 once they pull that change. And every such contributor, when doing bundle install on their own projects would also see the BUNDLED WITH version updated, making this a cascading effect. That sounds to me like a recipe for a really not smooth migration forcing everyone to update in a really short time, so that’s why I think Bundler should not touch the Gemfile.lock if there would be no other change than the BUNDLED WITH version (#4).

On the other hand, it seems fine to use BUNDLED WITH 2.x if there is no Gemfile.lock or the dependencies need to be re-resolved due to, e.g., adding a gem, since Bundler 2 might resolve gems differently than Bundler 1.

So #1 and #3 seem mostly done for the latest 2.x release, but #2 and #4 are not yet addressed.

Does that sound like a good migration plan to you?

Would you be willing to help making these changes in Bundler and RubyGems?