The Art of Writing Code that Looks Good

Beautiful Code

Brujo Benavides, @elbrujohalcon

Insufferable Code Reviewer

Macro Hater

Amateur AST Interpreter

Already Too Old for AI

@elbrujohalcon

Beautiful Code

Beautiful Code

OH, MY GOD!! Who wrote this utterly incomprehensible piece of…

 

…code??!?!.

Ugly Code

– Brujo Benavides,
many times each month

Of course! It was me!

Ugly Code

– Brujo Benavides,
many times each month, too

Else-less ifs

up_to(I, Top) when Top >= I ->
  I rem 3 == 0 andalso io:format("fizz"),

  [io:format("buzz") || I rem 5 == 0],

  catch if I rem 3 /= 0 andalso I rem 5 /= 0 ->
      io:format("~p", [I])
  end,

  io:format(" "),
  up_to(I + 1, Top);
up_to(I, Top) when Top < I ->
  io:format("~n").

Case Statements

up_to(I, Top) when Top >= I ->
    case I of
        I when I rem 3 == 0 -> io:format("fizz");
        _ -> noop
    end,
    case I of
        I when I rem 5 == 0 ->
            io:format("buzz");
        _ -> noop
    end,
    case I of
        I when I rem 3 /= 0 andalso I rem 5 /= 0 ->
            io:format("~p", [I]);
        _ -> noop
    end,
    io:format(" "),
    up_to(I + 1, Top);
 up_to(I, Top) when Top < I ->
    io:format("~n").

Case Statement

up_to(I, Top) when Top >= I ->
    case {I rem 3, I rem 5} of
        {0, 0} -> io:format("fizzbuzz");
        {0, _} -> io:format("fizz");
        {_, 0} -> io:format("buzz");
        {_, _} -> io:format("~p", [I])
    end,

    io:format(" "),
    up_to(I + 1, Top);
 up_to(I, Top) when Top < I ->
    io:format("~n").

Auxiliary Funs

up_to(I, Top) when Top >= I ->
    print(I),
    io:format(" "),
    up_to(I + 1, Top);
 up_to(I, Top) when Top < I ->
    io:format("~n").

print(I) when I rem 3 == 0, I rem 5 == 0 ->
	io:format("fizzbuzz");
print(I) when I rem 3 == 0 ->
	io:format("fizz");
print(I) when I rem 5 == 0 ->
	io:format("buzz");
print(I) ->
	io:format("~p", [I]).

All the Tests!

-export([one/1, two/1, three/1, four/1, …]).
-export([all/0]).
-export([init_per_suite/1, end_per_suite/1, …]).

…

all() -> [one, two, three, four, …].

…
one(_) -> ….

Export all things!

-compile([export_all]).

…

all() -> [one, two, three, four, …].

…

one(_) -> ….

Macros

-define(EXCLUDED_FUNS,
        [module_info, all, init_per_suite,
         end_per_suite, an_auxiliary_function]).

…

all() ->
    Exports = ?MODULE:module_info(exports),
    [F || {F, _} <- Exports
        , not lists:member(F, ?EXCLUDED_FUNS)].

Filtermap

all() ->
    lists:filtermap(
      fun ({module_info,_}) -> false;
          ({all,_}) -> false;
          ({init_per_suite,1}) -> false;
          ({end_per_suite,1}) -> false;
          %% …many many more… %%
          ({FName,1}) -> {true, FName};
          ({_,_}) -> false
      end, ?MODULE:module_info(exports)).

Parse Transform

-compile({parse_transform, my_ct_helper}).
-module(my_ct_helper).
…
parse_transform(Forms,_Options) ->
    FormsInfo =
    	erl_syntax_lib:analyze_forms(Forms),
    Functions =
    	proplists:get_value(
        	functions, FormsInfo, []),
    OrigExports =
    	proplists:get_value(exports, FormsInfo, []),
    % …a lot more cryptic code that adds all/0… %
    Out.
define Beauty {

	...
    
}

Guidelines

  • Formatters
  • Linters
  • Language Servers

Tooling

  • Maintain Existing Style (from Inaka and Sean Geodeke)
  • Top-Down (from the OTP Team)
  • The principle of "Least Astonishment" (from the OTP Team)
  • The scourge of 'undefined' (from Jesper)
  • The values ‘true’ and ‘false’ are of type atom() (by Jesper)
  • …and many others

"Manual" Rules

  • Maintain Existing Style (from Inaka and Sean Geodeke)
  • Top-Down (from the OTP Team)
  • The principle of "Least Astonishment" (from the OTP Team)
  • The scourge of 'undefined' (from Jesper)
  • The values ‘true’ and ‘false’ are of type atom() (by Jesper)
  • …and many others

"Manual" Rules

  • Maintain Existing Style (from Inaka and Sean Geodeke)
  • Top-Down (from the OTP Team)
  • The principle of "Least Astonishment" (from the OTP Team)
  • The scourge of 'undefined' (from Jesper)
  • The values ‘true’ and ‘false’ are of type atom() (by Jesper)
  • …and many others

"Manual" Rules

  • Maintain Existing Style (from Inaka and Sean Geodeke)
  • Top-Down (from the OTP Team)
  • The principle of "Least Astonishment" (from the OTP Team)
  • The scourge of 'undefined' (from Jesper)
  • The values ‘true’ and ‘false’ are of type atom() (by Jesper)
  • …and many others

"Manual" Rules

Not much to explain here, right? 😎

Code Reviews

Not much to explain here, right? 😎

Code Reviews

According to our coding guidelines (Particularly #123), we don't use parse_transforms to inject new functions in our modules. Please just write the all/0 function explicitly. Thanks.

CR Solver

– A Reviewer,
from A Place with Guidelines

To create code that's as beautiful as possible…

  • Use the tools at your disposal. Use…
    • a Formatter
    • a Linter
    • a Language Server
  • In fact, use all the tools that you can…
    • Hank, Xref, Dialyzer, …

Takeaways

To create code that's as beautiful as possible…

  • Decide what it means for code to be beautiful
    • and write that down somewhere
  • Use that framework to review pull requests
    • and when new debates are settled…
      • add new guidelines to your list!
  • Whenever possible, share them with the community

Takeaways

Thank You!

@elbrujohalcon - about.me/elbrujohalcon

As an open-source maintainer of many developer tools, let me talk about my recent experiences in this area…

Over the last 10 years (or more), working at different companies (like Inaka, ESL, and NextRoll), with multiple friends and colleagues, we've built a linter, a formatter, a better XRef runner, an oxbow code detector, a spellchecker, a dependency updater, a better interface for TypER, a comprehensive public set of guidelines, …

Bonus RANT

As an open-source maintainer of many developer tools, let me talk about my recent experiences in this area…

Over the last 10 years (or more), working at different companies (like Inaka, ESL, and NextRoll), with multiple friends and colleagues, we've built a linter, a formatter, a better XRef runner, an oxbow code detector, a spellchecker, a dependency updater, a better interface for TypER, a comprehensive public set of guidelines, …

Recently, WhatsApp is building all of these tools from scratch, again…

Bonus RANT

I found two main reasons…

  1. The tools we've built were not popular enough.

Why?

I found two main reasons…

  1. The tools we've built were not popular enough.
  2. The tools that WhatsApp is building do not rely on OTP.

Why?

Why?

Why?

Full story here and here.

Development tools should be actively maintained by the OTP Team and using them should be proactively recommended to all developers.

The Ideal World

Development tools should be actively maintained by the OTP Team and using them should be proactively recommended to all developers.

  • We should not rely on the community to maintain these tools.

The Ideal World

Development tools should be actively maintained by the OTP Team and using them should be proactively recommended to all developers.

  • We should not rely on the community to maintain these tools.
  • If we do, OTP should at least provide the building blocks…
    • Complete and usable parsers
    • Formatters that are updated as new things are added to the language
    • Support for language elements that would help implement these tools

The Ideal World

Made with Slides.com