Juha-Matti Santala - Community Builder. Dreamer. Adventurer.
2024-03-18T00:00:00Z
Juha-Matti Santala
juhamattisantala@gmail.com
My most used bookmarklets
2024-03-18T00:00:00Z
https://hamatti.org/posts/my-most-used-bookmarklets/
<p>
Last week I shared
<a href="https://hamatti.org/posts/search-directly-on-website-with-firefox-bookmark-keywords/" class="notion-text-href">my love for Firefox’s Keyword search feature</a>. This week, I want to expand that love to bookmarklets.
</p>
<p>
<a href="https://adactio.com/journal/20965" class="notion-text-href">Jeremy Keith wrote about his collection of useful bookmarklets for testing
websites</a>. That inspired me to take a look at my bookmarklets and how I use the
technology. I love how they are kind of like lightweight extensions without
the need to submit them to any markets, work the same on all browsers and are
quicker to develop.
</p>
<p>
They are also often very personal: it requires intention to click it to
activate so I can rely on myself only running them in the right context and
don’t need to write defensive code (like error handling), making the
bookmarklets simpler.
</p>
<p>
Caio Rordrigues’
<a href="https://caiorss.github.io/bookmarklet-maker/" class="notion-text-href">Bookmarklet Maker</a>
makes creating these bookmarklets a breeze.
</p>
<p>
I tend to save my bookmarklets in full Javascript form in my GitHub, Gists or
Obsidian notes so I can adjust them without needing to reverse engineer
minified bookmarklet code.
</p>
<h2 class="notion-heading_2 notion-color-default">
Copy links in different formats
</h2>
<p>
I copy links to be stored or used in other places all the time. I keep my
digital notes in Markdown format,
<a href="https://hamatti.org/uses/obsidian" class="notion-text-href">using Obsidian</a> and I
want my links there to be more descriptive instead of just pure URLs that are
often not very human-friendly.
</p>
<p>
So when I read
<a href="https://joonakeskitalo.github.io/2024/01/09/quickly-copying-data-from-browser-to-notes/" class="notion-text-href">Joona’s blog post about his bookmarklet</a>
that copies the title and URL from the website ready for his notes, it was
like Christmas to me. I modified it slightly and now I have two versions of it
in my bookmarks toolbar.
</p>
<p>
First one copies it in Markdown format as
<code class="notion-text-code">[Site title](site url)</code> so I can use it
in my notes and another one that copies it in HTML format
<code class="notion-text-code"><a href="site url">Site title<a></code>
that I can use on my website(s).
</p>
<p>
When I first shared this in Mastodon,
<a href="https://indieweb.social/@timokoola/111748476306882160" class="notion-text-href">Timo mentioned his similar approach</a>
that also enables highlighting text to have it included in blockquote. I have
implemented that in my Markdown version.
</p>
<h3>Markdown version</h3>
<pre class="language-javascript"><code class="language-javascript">(() => {
const copy = (title, url, selection) => {
const textArea = document.createElement("textarea");
let text = "";
if (selection && selection != "") {
text = `> ${selection}\n`;
}
text += `[${title}](${url})`;
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand("copy");
} catch (err) {
console.error("Copying err", err);
}
document.body.removeChild(textArea);
};
copy(document.title, window.location.href, window.getSelection());
})();</code></pre>
<h3>HTML version</h3>
<pre class="language-javascript"><code class="language-javascript">(() => {
const copy = (title, url) => {
const textArea = document.createElement("textarea");
let text = `<a href="${url}">${title}</a>`;
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand("copy");
} catch (err) {
console.error("Copying err", err);
}
document.body.removeChild(textArea);
};
copy(document.title, window.location.href);
})();</code></pre>
<h2 class="notion-heading_2 notion-color-default">Liiga.fi highlighter</h2>
<p>
The Finnish hockey league
<a href="https://liiga.fi/fi" class="notion-text-href">Liiga has a website</a>
that has a lot of stuff and I keep heavily adjusting it with custom CSS and a
custom Javascript functionality provided by this bookmarklet to keep it more
usable.
</p>
<img src="https://hamatti.org/assets/img/posts/my-most-used-bookmarklets/1.png" alt=" " />
<p>
I use custom CSS to hide everything else from the main site than schedule /
current games on the left and the standings on the right. With a bookmarklet,
I add a functionality where hovering over a game on the schedule highlights
the teams in the standings.
</p>
<p>
This bookmarklet breaks about all the time because the underlying HTML keeps
changing. The standings table is implemented as a ton of
<code class="notion-text-code">span</code>s instead of actual table so I’m
constantly adjusting the code.
</p>
<pre class="language-javascript"><code class="language-javascript">/**
* A bookmarklet to highlight teams in standings in Liiga.fi
* You can use https://caiorss.github.io/bookmarklet-maker/ to turn this into a minified bookmarklet
*/
/**
* Injects our custom stylesheet into the `<head>`
*
* It will turn custom-highlight colors to black background and white background
* and adjusts a few generated classes to force inherited color.
*
* It's very likely these dynamic class names change regularly so this will probably break all the time.
*/
function addCustomStyles() {
const style = document.createElement("style");
style.innerText = `.custom-highlight { background: black !important; color: white !important; } ._sairaTeamName_yum7u_18, ._dataElement_yum7u_388 { color: inherit !important;}`;
document.querySelector("head").appendChild(style);
}
addCustomStyles();
const standings = document.querySelector("#main-standings-container");
/**
* Removes custom-highlight class from everywhere
*/
function clearHighlights() {
const highlighted = document.querySelectorAll(".custom-highlight");
Object.values(highlighted).forEach((node) => {
node.classList.remove("custom-highlight");
});
}
/**
* When hovering over a match in the schedule, will highlight
* the teams of the provided match argument in the standings table
*
*/
function onHover(teams) {
// First we clear all previous highlights
clearHighlights();
// Find the correct team nodes in the standings table
const teamSpans = document.querySelectorAll("#standing-grid-team");
const currentTeams = Object.values(teamSpans).filter((teamSpan) => {
let name = teamSpan.querySelector("span:last-child").textContent;
return teams.includes(name);
});
// For teams found, we traverse the span elements in the page
// and currently the amount of spans is first + 6 elements.
// Add custom-highlight class to each and remove background-color inline style
// if one exists
currentTeams.forEach((node) => {
node.classList.add("custom-highlight");
node.style.removeProperty("background-color");
for (let i = 0; i < 6; i++) {
node = node.nextSibling;
node.classList.add("custom-highlight");
node.style.removeProperty("background-color");
}
});
}
const matches = document.querySelectorAll(
"#latest-games-card-container > div:not(:first-child)"
);
matches.forEach((match) => {
match.addEventListener(
"mouseover",
(event) => {
// I'm using a bit of a hack here to get the team names from alt texts.
// It's the least likely part to break when they change something in the DOM.
const teams = Object.values(match.querySelectorAll("img")).map(
(img) => img.alt
);
onHover(teams);
},
{
capture: true,
}
);
});</code></pre>
<p>
My accompanying CSS that I inject with
<a href="https://addons.mozilla.org/en-US/firefox/addon/styl-us/" class="notion-text-href">Stylus extension</a>:
</p>
<pre class="language-css"><code class="language-css">#main-right-container {
display: none !important;
}
._container .leftContainer {
flex-direction: row;
gap: 4em;
justify-self: start;
}
#main-left-container > div {
min-width: 400px;
}
#main-partners-banner-container {
display: none;
}</code></pre>
<h2 class="notion-heading_2 notion-color-default">
Opening blog posts to edit in VS Code
</h2>
<p>
This is a feature I picked up from
<a href="https://lea.verou.me/blog/2023/going-lean/#open-file-in-vs-code-from-the-browser%3F" class="notion-text-href">Lea Verou’s blog post Going Lean</a>.
</p>
<p>
Since my blog posts URL path maps to my Eleventy project relatively well, I
can use <code class="notion-text-code">vscode://</code> protocol with a
bookmarklet to open any blog post on my website into my website project in VS
Code. Mind blown.
</p>
<pre class="language-javascript"><code class="language-javascript">location.href = `vscode://file/[path/to/my/repository]/${(new URL(location.href)).pathname.slice(0, -1)}.njk`;</code></pre>
<p>
For this one, I pick up the current URL’s pathname, remove trailing slash and
add <code class="notion-text-code">.njk</code> extension so it maps to my
code. A great part of this is that it works both when I’m working locally in
localhost, previewing in Netlify’s staging environment or browsing my site in
production.
</p>
<p></p>
Syntax Error #13: Playgrounds
2024-03-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-13-playgrounds/
<p>
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. For this March issue, I wrote about online
playgrounds as a method for debugging and sharing code. Read full article at
<a href="https://www.syntaxerror.tech/syntax-error-13-playgrounds">syntaxerror.tech/syntax-error-13-playgrounds</a>
and either subscribe to the email or RSS feed to catch all of them.
</p>
On content creation and personal web
2024-03-16T00:00:00Z
https://hamatti.org/posts/on-content-creation-and-personal-web/
<h2 class="notion-heading_2 notion-color-default">Build yourself a website!</h2>
<p>
Ana Rodriquez published a blog post titled
<a href="https://ohhelloana.blog/just-get-a-website/" class="notion-text-href">You don’t have to be a “content creator” to have a website</a>
last Thursday. It’s a great piece and I wholeheartedly agree with the main
message.
</p>
<p>
Having a personal website that is not controlled by commercial platforms that
is accessible via your own domain is such a powerful position to be in. Even
if at the beginning the content on that site is just your name and contact
information and links to other platforms.
</p>
<p>
It’s powerful because you control it and you can expand it. You can link to
your own website and not have to worry about Facebook or X or LinkedIn or
Medium putting your stuff behind a login or paywall. With one domain, you can
change where things link to without having to let everyone know things have
moved around.
</p>
<p>I love the ending note:</p>
<blockquote class="notion-quote notion-color-default">
Give yourself permission to exist and be seen regardless of whether you have a
blog, side projects or “content” - whatever it means.
</blockquote>
<h2 class="notion-heading_2 notion-color-default">
Content creation in personal web
</h2>
<p>But there’s also the other part which is the label of “content creator”.</p>
<blockquote class="notion-quote notion-color-default">
This is clearly the result of living in a capitalist society. In recent years,
people have felt the pressure to monetise their hobbies, so there’s a constant
state of hustle. We all need money to exist in our society.
</blockquote>
<p>
I do love that we’re having this discussion about reducing the need to make
everything and every hobby a money making machine.
</p>
<p>
In
<a href="https://silviamaggidesign.com/notes/note-content-creation/" class="notion-text-href">her reply to Ana’s blog post, Silvia writes</a>:
</p>
<blockquote class="notion-quote notion-color-default">
A photo, a post, a video, a podcast. These are among the things that are now
called ’content‘ on social media. You create content and present it to your
followers the right way, at the right time. Have you tried monetise it? Well,
wouldn’t it be great to be paid to do what you love? It became common thinking
that turning your hobbies into profit is a great idea.
</blockquote>
<p>
In the personal web bubble that I hang out in, I’ve been hearing a lot of
dissidence against the label of “content” or “content creation” as people
often see it as part of the aforementioned hustle culture.
</p>
<p>
I have started to use it less and less these days but I also struggle to come
up with another label that would fit someone like me who might be writing blog
posts, write newsletters, give talks in conferences and meetups, visit
podcasts or teach programming. All those various forms of content that I
create. I don’t quite consider myself an artist because most of the stuff is
not art even when it’s on the spectrum of creativity.
</p>
<h2 class="notion-heading_2 notion-color-default">
Avoid the blank page syndrome
</h2>
<p>
Building a very barebones website is a great way to beat
<a href="https://www.ool.co.uk/blog/blank-page-syndrome-and-how-to-beat-it/" class="notion-text-href">the blank page syndrome</a>. When you have something that exists and is published, making tiny
improvements and additions become easier. Over time, these things accumulate
and at some point you realize you have a great website that you’re excited to
share with others.
</p>
<p>
I’ve been building this current iteration of my website for almost 6 years
now. It’s never complete or perfect or quite right but I can write blog posts
and publish them there. And whenever the inspiration hits, I can do small
fixes and new feature additions.
</p>
<p>
For example, most recently
<a href="https://hamatti.org/posts/display-full-url-after-link-when-page-is-printed/" class="notion-text-href">I improved my print styling</a>. Before that
<a href="https://hamatti.org/posts/search-webmentions-and-microformats/" class="notion-text-href">I added search, Webmentions, microformat support and blog stats page</a>. Before that,
<a href="https://hamatti.org/posts/workaround-for-notions-lack-of-heading-levels/" class="notion-text-href">I improved my tooling to allow more levels of headings in my blog</a>. And
<a href="https://hamatti.org/posts/showing-most-popular-posts-with-netlify-analytics/" class="notion-text-href">most popular posts</a>. And
<a href="https://hamatti.org/posts/custom-cookie-consent-for-video-embeds/" class="notion-text-href">custom cookie consent for Youtube embeds</a>
(that are still broken for RSS, oopsie). And
<a href="https://hamatti.org/posts/building-dark-mode-for-hamatti-org/" class="notion-text-href">added dark mode</a>. At one point
<a href="https://hamatti.org/posts/blog-comments-via-mastodon/" class="notion-text-href">I made Mastodon commenting possible for selected posts</a>.
</p>
<p>
These sound like a lot when shared in one paragraph but they have happened
over 8 months. It’s always easier improving something that exists than trying
to implement all of these from the start.
</p>
<p>You can do it too. I believe in you!</p>
Search directly on a website with Firefox bookmark keywords
2024-03-13T00:00:00Z
https://hamatti.org/posts/search-directly-on-website-with-firefox-bookmark-keywords/
<p>
Let’s say you’re a Pokemon TCG player and you regularly use the great
<a href="https://pkmncards.com/" class="notion-text-href">PkmnCards website</a>
to search information about cards.
</p>
<img src="https://hamatti.org/assets/img/posts/search-directly-on-website-with-firefox-bookmark-keywords/1.png" alt="PkmnCards website’s header with a search bar " />
<p>
You can head over to the site, find the search field, write your query and hit
enter.
</p>
<p>
When done occasionally, that’s a good enough flow. But when you start using
them all the time, multiple times a day, there’s a better way.
</p>
<p>
On
<a href="https://www.mozilla.org/en-US/firefox/new/" class="notion-text-href">Firefox</a>, you can right click on any search input box and choose
<b class="notion-text-bold">Add Keyword for this Search…</b>
</p>
<img src="https://hamatti.org/assets/img/posts/search-directly-on-website-with-firefox-bookmark-keywords/2.png" alt="PkmnCards website with context menu opened on the search field. Shows “Add a Keyword for this Search…” as one option. " />
<p>
Then, you can add a custom keyword into the
<b class="notion-text-bold">Keyword</b> field. I chose
<i class="notion-text-italic">pkmn</i> for this site.
</p>
<img src="https://hamatti.org/assets/img/posts/search-directly-on-website-with-firefox-bookmark-keywords/3.png" alt="Firefox Add bookmark view with name field filled with “Search PkmnCards < Pokemon TCG Card Search / Database” and keyword field filled with “pkmn” " />
<p>
The next time you’re about to search, type
<code class="notion-text-code">pkmn [query]</code> into your Firefox’s URL bar
and it will automatically run the search.
</p>
<p>
This is one of my favourite features in Firefox these days. I use it all the
time.
</p>
<p>Some examples:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">y [query]</code> for Youtube
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">pkmn [card name]</code> for PkmnCards
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">t [page number]</code> for Yle Tekstitv (for
example, <code class="notion-text-code">t 221</code> goes directly to
<a href="https://yle.fi/aihe/tekstitv?P=221" class="notion-text-href">https://yle.fi/aihe/tekstitv?P=221</a>)
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">imdb [query]</code> for IMDB.com
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">steam [query]</code> for Steam
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">jw [movie]</code> for Just Watch
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">w [query]</code> for Wikipedia
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">bgg [game]</code> for Board Game Geek
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">comp [query]</code> for Pokemon TCG Rulings
Compendium
</li>
</ul>
<h2 class="notion-heading_2 notion-color-default">Use without a search box</h2>
<p>
The <b class="notion-text-bold">Add Keyword for this Search… </b>context menu
item is available for search input fields but it’s just a helper shortcut. If
you want to use it for any URL, you can do
<code class="notion-text-code"><a href="https://yle.fi/aihe/tekstitv?P=%25s" class="notion-text-href">https://example.com/%s</a></code>
where the query passed after the keyword is put in the place of
<code class="notion-text-code">%s</code> in the URL.
</p>
<p>
Note that all of these are just direct URL string replacements. This means
that these only work with searches that use URLs and not for example ones that
run Javascript to modify the page in-place.
</p>
Thoughts on accessibility in personal web
2024-03-11T00:00:00Z
https://hamatti.org/posts/thoughts-on-accessibility-in-personal-web/
<p>
This month’s
<a href="https://indieweb.org/indieweb-carnival" class="notion-text-href">IndieWeb Carnival</a>
is hosted by
<a href="https://blog.basementcommunity.com/accessibility-in-the-personal-web/" class="notion-text-href">orchids</a>
on the topic of accessibility in small web:
</p>
<blockquote class="notion-quote notion-color-default">
Learning everything about accessibility is not a realistic goal, but making a
consistent effort to make your Websites more inclusive is. Regardless of
whether you're working on a personal Website with flashing gifs and numerous
graphics, or a sterile professional Website, each project might have its own
unique usability problems that need to be solved, and the first step in
addressing those is understanding that you don't know everything, while taking
your best crack at learning about your users' disabilities.
</blockquote>
<p>
Accessibility is a topic close to my heart and I’ve been learning about it
over the past few years. I’m still a long way from being an expert and this
website has its own issues but I aim to improve things a little by little as I
learn more.
</p>
<p>
Here is a collection of thoughts that the theme sparked in me when I was
thinking of the small web sites that I’ve been building.
</p>
<h2 class="notion-heading_2 notion-color-default">
Offering multiple ways to read my content
</h2>
<p>
On a desktop, the view on an individual blog post is quite busy. There’s the
post itself but also a sidebar with links to various other posts in my blog
and other people’s blogs. I like that interconnectedness and helping readers
find other things to read as well. But I realise it can be bit much for some.
There are a few options available.
</p>
<p>
Most mainstream browsers offer a reading mode that offers a distraction free
way to enjoy my blog posts. If you’re browsing this on a Firefox, you should
see a reading mode icon at the end of the URL bar. This hides all the extra
elements on the page and only shows the main content of the blog post and uses
only browser’s own stylesheet. This is a great option for when you want or
need a more focused experience.
</p>
<p>
Alternatively, one can subscribe to my blog via RSS and use tools that work
best for them in reading my writing. I share the entire post in the RSS feed
so one can read my blog without ever entering my website after they have
subscribed to the feed.
</p>
<p>
If someone wants to read my blog posts on paper while enjoying a hot cocoa,
sitting in their favourite reading nook, they can print out my blog posts. I
have done custom CSS styling for blog post prints. I hide all the excess
(header, footer, sidebar, hero image, newsletter promotion), make
<a href="https://hamatti.org/posts/display-full-url-after-link-when-page-is-printed/" class="notion-text-href">the URLs visible</a>
inside parenthesis after each link.
</p>
<h2 class="notion-heading_2 notion-color-default">The UX of HTML</h2>
<p>
For a developer of small web sites, the easiest way to have a good, solid
starting point for accessibility is to use semantic HTML - or follow the
<a href="https://www.htmhell.dev/adventcalendar/2023/1/" class="notion-text-href">UX of HTML</a>
as Vasilis van Gemert framed it in his great blog post.
</p>
<p>
Semantic HTML means using the correct HTML elements/tags for the right use
cases. The native elements are best supported by browsers and assistive
technology tools and they work in a way the users are familiar with.
</p>
<p>
As
<a href="https://www.w3.org/2001/tag/doc/leastPower.html" class="notion-text-href">The Rule of Least Power</a>
suggests, I think it’s a good aim to build things with the least powerful
technology that can achieve it.
</p>
<blockquote class="notion-quote notion-color-default">
When designing computer systems, one is often faced with a choice between
using a more or less powerful language for publishing information, for
expressing constraints, or for solving some problem. This finding explores
tradeoffs relating the choice of language to reusability of information. The
"Rule of Least Power" suggests choosing the least powerful language suitable
for a given purpose.
</blockquote>
<p>
Relying on standards does a lot of heavy lifting here in the personal web.
Using the correct HTML elements leads to less work trying to patch the
accessibility back in with Javascript for example.
</p>
<p>
A pet peeve of mine is using the right heading levels. I don’t know if it’s
HTML/CSS or Microsoft Word that got us into the habit of using a heading level
that looks the way that matches what we want to see rather than styling it
separately but it’s very common.
</p>
<p>
People browsing the website with assistive technologies like a screen reader
will end up confused with inconsistent heading levels.
</p>
<h2 class="notion-heading_2 notion-color-default">
Respecting user preferences
</h2>
<p>
Browsers offer users a few ways to communicate their preferences that websites
can then respect or not.
</p>
<p>
A user can tell their browser they prefer a dark mode of the website and this
can be read programmatically with
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme" class="notion-text-href">prefers-color-scheme</a>
in Javascript and CSS. So if your site has both light and dark themes, you can
make it so that the user is automatically provided the version they prefer.
</p>
<p>
Another one, that orchirds also talks a bit in their blog post, is
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" class="notion-text-href">prefers-reduced-motion</a>
which communicates if the user prefers (or in many cases, needs) to have an
experience where there’s not fast moving animations and other moving pieces on
the site to have a decent browsing experience. To accomdate their needs, the
website can turn off animations when the user has selected that preference.
</p>
<p>
This way, you don’t have to completely turn everything off but you can be
mindful of your readers’ needs.
</p>
<h2 class="notion-heading_2 notion-color-default">
Alternative texts on images
</h2>
<p>
I strive to have a good
<a href="https://webaim.org/techniques/alttext/" class="notion-text-href">alt text </a>on the images I have on this site and whenever I share images in social
media.
</p>
<p>
One tech hack that I have to help make sure I don’t forget writing them is to
have a custom CSS rule (using
<a href="https://addons.mozilla.org/en-US/firefox/addon/styl-us/" class="notion-text-href">Stylus</a>
browser extension) that is active on localhost addresses and adds an orange
outline on any element that is missing an alt text:
</p>
<pre class="language-css"><code class="language-css">img:not([alt]), img[alt=""] {
outline: 2px solid orange;
}</code></pre>
<p>
This way, when I’m previewing my blog posts or new pages in the site in
development mode, I see all the missing ones. This does not guarantee that
they are good but at least it helps me spot the ones that are completely
missing.
</p>
<p>
It also highlights those that have an empty alt text. An empty alt text can be
a valid one so they are not all incorrect but since I rely on tooling to
render my HTML for blog posts, I want the safeguard to notice them if that
starts accidentally happening. I can then ignore the ones that I know should
have an empty alt text.
</p>
<h2 class="notion-heading_2 notion-color-default">
Personal site as a platform for learning
</h2>
<p>
For a lot of us, personal websites are a great enabler in learning.
<a href="https://matthiasott.com/articles/into-the-personal-website-verse" class="notion-text-href">Matthias Ott writes about the topic</a>
in a way that I agree on:
</p>
<blockquote class="notion-quote notion-color-default">
Building things for your own site is so worthwhile because you are allowed to
make mistakes and learn without pressure. If it doesn’t work today, well,
maybe it’ll work tomorrow. It doesn’t matter.
</blockquote>
<p>
Another aspect is that there’s nobody else prioritizing your work. You get to
choose what new features or improvements you work on in your own platform. If
you learned about a new web API or accessibility feature, you can implement it
on your site and keep learning.
</p>
<p>
Accessibility is never done and finished. So I keep an open mind towards
learning new things and making small improvements to make web more accessible
to everyone.
</p>
Chesterton’s Fence and tech documentation
2024-03-06T00:00:00Z
https://hamatti.org/posts/chestertons-fence-and-tech-documentation/
<h2 class="notion-heading_2 notion-color-default">
What’s Chesterton’s Fence?
</h2>
<p>
<a href="https://en.wiktionary.org/wiki/Chesterton%27s_fence" class="notion-text-href">Chesterton’s Fence</a>
is a concept in public policy that states:
</p>
<blockquote class="notion-quote notion-color-default">
The principle that reforms should not be made until the reasoning behind the
existing state of affairs is understood.
</blockquote>
<p>
I ran into it for the first time a while back and it immediately took my
thoughts to documentation of software projects.
</p>
<p>
<a href="https://sproutsschools.com/chesterton-fence-dont-destroy-what-you-dont-understand/" class="notion-text-href">An article at Sprouts</a>
defines it in more layman’s terms as
</p>
<blockquote class="notion-quote notion-color-default">
Chesterton’s Fence is a simple rule of thumb that suggests that you should
never destroy a fence, change a rule, or do away with a tradition until you
understand why it’s there in the first place. The principle assumes that
fences have a purpose, were carefully planned, and cost time and money to
erect. Someone must have had a reason for thinking that a fence would be a
good idea. So what if we just take them down?
</blockquote>
<p>
Before I knew about this named concept, I used to use a “pizza in office
fridge” example:
</p>
<blockquote class="notion-quote notion-color-default">
Imagine you come to the office one morning and see a few pizza slices in an
unmarked container. You could eat them (especially if it’s common to have
leftovers from events), throw them to bio bin or ignore them. But since they
are unmarked, you can’t know when they were put there (are they still
edible?), do they belong to someone or are they up for grabs and what kind of
pizzas they are. So unless more instructions follow, the safest bet is to
ignore them. This can lead to the fridge being full of pizzas someone meant
for their colleagues to eat but without instructions, nobody knows what to do.
</blockquote>
<h2 class="notion-heading_2 notion-color-default">
What does this has to do with documentation?
</h2>
<p>
I have been to various projects where the lack of documentation caused an
issue where nobody in the team remembered or knew why something, initially
weird looking, was implemented the way it was.
</p>
<p>
Even a bit of documentation would have helped so much: if we knew why it was
originally done that way, we could have considered if situation had changed
and if it was safe to remove or change.
</p>
<p>
In my talk
<a href="https://hamatti.org/talks/contemporary-documentation/" class="notion-text-href">Contemporary Documentation</a>, I’ve talked to developers about how and why to document decisions when they
are being made, and stored in a format (in this case, version control) that
doesn’t erase the old decisions as software evolves.
</p>
<p>
In an optimal case, we’d have full test coverage (in terms of use cases, not
necessarily code lines executed) but that’s kind of a pipe dream in most
software projects. With a perfect coverage, we could more confidently remove
things and see if anything breaks and adjust accordingly.
</p>
<p>
But with imperfect one, we are left with a difficult consideration: do we take
the risk of breaking the fence which can lead to bigger, unexpected problems
down the road or do we ignore it, being on the safer side but maybe slowing us
down with new features or inefficiencies in the software.
</p>
<p>
Keeping the fence in mind when you write software and its documentation,
especially internal one, can save you a lot of headache in the future.
</p>
Display full URL after a link when a page is printed
2024-03-04T00:00:00Z
https://hamatti.org/posts/display-full-url-after-link-when-page-is-printed/
<p>
When browsing on the web, we hide links behind words that describe them to
make text flow better. But when you print out a web page, by default all you
see is all those links with no clue where they lead to, making them completely
useless.
</p>
<p>
Last weekend I worked on improving the print styling for my blog posts and
came across a need to fix this.
</p>
<p>
Thankfully, Chris Coyier shared
<a href="https://css-tricks.com/snippets/css/print-url-after-links/" class="notion-text-href">a snippet in CSS-Tricks</a>
- checks notes - 15 years ago.
</p>
<pre class="language-css"><code class="language-css">@media print {
a::after{
content: " (" attr(href) ") ";
}
}</code></pre>
<p>
This will change a link like
<a href="https://hamatti.org/" class="notion-text-href">my website</a> into
<a href="https://hamatti.org/" class="notion-text-href">my website (https://hamatti.org)</a>.
</p>
<p>
However, not all my links are absolute URLs. Whenever I really link to
anything on my website, I use relational urls and they can end up looking like
<code class="notion-text-code">search (/search)</code> which is not optimal.
In Chris’ post, there was
<a href="https://css-tricks.com/snippets/css/print-url-after-links/#comment-1765009" class="notion-text-href">a question from 2020</a>
about how to do this but without a proper answer.
</p>
<p>Here’s how I’ve solved that:</p>
<pre class="language-css"><code class="language-css">@media print {
a[href^="/"]::after {
content: " (https://hamatti.org" attr(href) ") ";
}
}</code></pre>
<p>
For each link that starts with <code class="notion-text-code">/</code>, I
manually add the URL into the string. This way, the example above would turn
into a
<code class="notion-text-code">search (https://hamatti.org/search)</code> and
be much more usable for anyone reading it later.
</p>
<p></p>
Search, Webmentions and microformats
2024-02-28T00:00:00Z
https://hamatti.org/posts/search-webmentions-and-microformats/
<p>
I have recently been updating this website with new features! Let’s talk about
them.
</p>
<h2 class="notion-heading_2 notion-color-default">Search</h2>
<p>
Inspired by
<a href="https://www.youtube.com/watch?v=v79VRrfVau8" class="notion-text-href">a Youtube video by Coding in Public</a>, I decided to add
<a href="https://hamatti.org/search" class="notion-text-href">a search function</a> to my site.
It’s actually something I’ve wanted to have for a long time but given this is
a purely static, generated site, I hadn’t found a good option. I didn’t want
to build something that would rely on 3rd party APIs or anything like that
because I want to keep my dependencies as small as possible.
</p>
<p>
From that video however, I learned about a tool called
<a href="https://pagefind.app/" class="notion-text-href">Pagefind</a> that
makes it possible to generate the search after the build and then made
available in the website.
</p>
<p>There are essentially two steps:</p>
<p>
First, you need to generate your search “database”. To do that, after building
the site with Eleventy, run
<code class="notion-text-code">npx pagefind --site=_site</code> (replacing
<code class="notion-text-code">_site</code> with wherever your site gets built
to).
</p>
<p>
Second, you need to add their script to the website itself. I made a new file
<code class="notion-text-code">search.njk</code> and added:
</p>
<pre class="language-html"><code class="language-html"><link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<div id="search"></div>
<script>
window.addEventListener('DOMContentLoaded', (event) => {
new PagefindUI({ element: "#search", showSubResults: true });
});
</script></code></pre>
<p>
The contents of <code class="notion-text-code">/pagefind</code> gets populated
automatically by the script in the first step. When the page is loaded, it
will create the search UI and populate the results.
</p>
<p>
The third, optional step, that I took, was to exclude some pages and elements
from the search. For example, I removed the blog listing page, the sidebar on
my blog that shows most recent and so on because those are items that the
search will find anyway through the main blog post or the blog roll page. To
do that, I added a
<code class="notion-text-code">data-pagefind-ignore</code> attribute to the
elements I wanted to ignore.
</p>
<p>
Finally, as a fourth optional step, I defined the provided CSS variables so
that they respect my light/dark mode styling:
</p>
<pre class="language-css"><code class="language-css">--pagefind-ui-primary: var(--color-text-primary);
--pagefind-ui-text: var(--color-text-primary);
--pagefind-ui-background: var(--color-background-primary);
--pagefind-ui-border: var(--brand);
--pagefind-ui-tag: var(--color-text-primary);</code></pre>
<p>
I really like how the search turned out. Integrating Pagefind was really easy
and their documentation is very nice. I often struggle to integrate tools into
my projects so I was so happy when things just worked out of the box with a
single command and I was able to fine-tune the end result with custom CSS and
excluding elements from the search.
</p>
<h2 class="notion-heading_2 notion-color-default">Webmentions</h2>
<p>
I finally integrated Webmentions to the site, at least partially. Using a
third-party service
<a href="https://webmention.io/" class="notion-text-href">Webmention.io</a>,
I’m able to receive Webmentions sent by other people and other services. To
broadcast this for tools, I added a
</p>
<pre class="language-html"><code class="language-html"><link
rel="webmention"
href="https://webmention.io/hamatti.org/webmention"
/></code></pre>
<p>
into the <code class="notion-text-code"><head></code> of my site. When
someone sends a mention to that endpoint, I can see it from my
<a href="http://webmention.io/" class="notion-text-href">Webmention.io</a>
dashboard and feel the dopamine hit.
</p>
<p>
Related to this, I also added
<a href="https://github.com/remy/wm" class="notion-text-href">Remy’s great webmentions CLI tool</a>
to my workflow. I didn’t automate it (at least yet) but once I merge in my new
blog post, I have two npm scripts I can run to let others know through their
webmention setup that I’ve mentioned them.
</p>
<pre class="language-json"><code class="language-json">"webmentions:debug": "npx @remy/webmention https://hamatti.org/feed/feed.xml --limit 1 --debug",
"webmentions:send": "npx @remy/webmention https://hamatti.org/feed/feed.xml --limit 1 --send",</code></pre>
<p>
The first one, <code class="notion-text-code">webmentions:debug</code> checks
my RSS feed’s latest post, looks through all the links to see if they support
receiving webmentions and does a dry run, letting me know what it found. The
second, <code class="notion-text-code">webmentions:send</code> runs the same
but actually sends the webmentions.
</p>
<p>
Currently I’m doing this manually and have the debug script there so I can
keep a bit of an eye on it to make sure it works (and to learn how it works).
Probably eventually I’ll add it to my post-build process. Big thanks to
<a href="https://localghost.dev/blog/sending-webmentions-from-a-static-site/" class="notion-text-href">Sophie at localghost for teaching me how</a>!
</p>
<p>
I’m already seeing the webmentions flow in and it makes me very happy. Not
only to see other people share my content but also to be part of this cool web
crowd.
</p>
<p>
So far, I haven’t set up any automation to show these webmentions on my site
but I’m working on it. I just first need to get a bunch of them so I can learn
a bit more how they work and what kind of data I have to play with before I
decide how I integrate them.
</p>
<h2 class="notion-heading_2 notion-color-default">Microformats</h2>
<p>
Slightly related to the webmention stuff, I also started adding microformats
stuff to my website, starting with my blog posts.
<a href="https://microformats.org/wiki/Main_Page" class="notion-text-href">Microformats</a>
are a way to annotate websites and content pieces in a standardized way.
</p>
<p>
For example, if someone parses the microformatted information from my earlier
blog post on
<a href="https://hamatti.org/posts/debugging-python" class="notion-text-href">Debugging Python</a>, they would see (among other data):
</p>
<pre class="language-json"><code class="language-json">{
"type": [
"h-entry"
],
"properties": {
"name": [
"Debugging Python"
],
"author": [
"Juha-Matti Santala"
],
"published": [
"Jan 13th, 2024"
],
"content": [
{
"html": " -- omitted -- "
}
],
... (other data omitted)
}
}</code></pre>
<p>
I’m still very new to microformats and their usage but to my understanding,
when I now send a webmention from one of my blog posts to someone else’s blog,
they’ll get this extra metadata alongside the mention. I think that’s pretty
fun.
</p>
<p>
I’m only just beginning to explore the microformats but at least now I have a
starting point deployed and can start making incremental improvements as I
learn more.
</p>
<h2 class="notion-heading_2 notion-color-default">
Oh, I also added blog stats!
</h2>
<p>
Another thing that I added that I talked about in Mastodon but haven’t
mentioned on the blog yet: blog stats. Inspired by the
<a href="https://rknight.me/blog/stats/" class="notion-text-href">Robb Knight’s stats page</a>,
<a href="https://coryd.dev/stats" class="notion-text-href">Cory Dransfeldt’s stats page</a>
and using
<a href="https://postgraph.rknight.me/" class="notion-text-href">Robb’s Eleventy Post Graph plugin</a>, I now have a
<a href="https://hamatti.org/blog/stats" class="notion-text-href">/blog/stats</a> page that keeps
track of my most popular blog posts for the month as well as annual data on
how many blog posts I’ve published and on which days, similar to the
contribution graph in GitHub.
</p>
<p>
If you take a look, you’ll discover that I’m very much a Wednesday blogger. I
try to maintain a steady pace of publishing every Wednesday. This year with
extra time and creative energy on my hands, I’ve added regular Saturday posts
and occasional Monday posts to the mix as well.
</p>
THE Eleventy Meetup Feb 20th
2024-02-26T00:00:00Z
https://hamatti.org/posts/the-eleventy-meetup-feb-20/
<p>
Last week we had
<a href="https://11tymeetup.dev/events/ep-18-building-community-and-online-activity/" class="notion-text-href">a great event</a>
at
<a href="https://11tymeetup.dev/" class="notion-text-href">THE Eleventy Meetup</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">
<b class="notion-text-bold">Building a meetup community site using Global Data Files, Juhis</b>
</h2>
<img src="https://hamatti.org/assets/img/posts/the-eleventy-meetup-feb-20/1.png" alt="A presentation slide with text “Community sites with Eleventy Global Data Files, 20.02.2024, The Eleventy Meetup” and a cute drawing of an opossum hanging from a red balloon. " />
<p></p>
<p>
<b class="notion-text-bold"><a href="https://www.youtube.com/watch?v=e_H4DxqAiyY" class="notion-text-href">Video</a></b><b class="notion-text-bold"> / </b><b class="notion-text-bold"><a href="https://hamatti.org/slides/global-data-files.pdf" class="notion-text-href">Slides</a></b>
</p>
<p></p>
<p>
I had the pleasure to give the first talk of the year. In January, I wrote a
blog post
<a href="https://hamatti.org/posts/community-websites-with-eleventy/" class="notion-text-href">Community websites with Eleventy</a>
and when Cory asked if I’d be interested in talking in the event, I knew what
to talk about. This gave me a good reason to think about the topic a bit more.
</p>
<p>
I’m a big fan of Eleventy (have been since 2018!) and in this talk I talked
about how I have built websites for
<a href="https://turkufrontend.fi/" class="notion-text-href">Turku ❤️ Frontend</a>
and
<a href="https://archipylago.dev/" class="notion-text-href">archipylago</a>
utilizing Eleventy’s Global Data Files feature.
</p>
<p>
Calendar, event history, sponsors, team and speaker hall of fame all live in
JSON files in <code class="notion-text-code">_data/</code> folder and can be
used in any template across the project.
</p>
<p>
I also talked a bit about the pros and cons of using Global Data Files versus
using a Content Management System. The main point I make is: JSON files offer
a horrible content editing experience but have zero extra dependencies to
third party services while CMSs usually are the opposite.
</p>
<h2 class="notion-heading_2 notion-color-default">
<b class="notion-text-bold">Using Eleventy to Gobble Up Everything I Do Online, Robb Knight</b>
</h2>
<img src="https://hamatti.org/assets/img/posts/the-eleventy-meetup-feb-20/2.png" alt="Pepe Silvia meme from Always Sunny in Philadelphia where Charlie Kelly is standing next to a wall filled with papers and red lines going from one to another. Charlie’s face has been replaced with Robb’s. " />
<p>
<b class="notion-text-bold"><a href="https://www.youtube.com/watch?v=e_87IF7KGgo" class="notion-text-href">Video</a></b><b class="notion-text-bold"> / </b><b class="notion-text-bold"><a href="https://rknight.me/blog/using-eleventy-to-gobble-up-everything-i-do-online/" class="notion-text-href">Blog post</a></b>
</p>
<p>
<a href="https://rknight.me/" class="notion-text-href">Robb’s work</a> has
been a big inspiration for me over the past months and it was great to share
the stage with him.
</p>
<p>
Robb delivered a great talk about how he uses Eleventy to showcase a variety
of collections on his website like his game and lego collection, a media
tracking blog, a link blog, a full archive of his mastodon posts, plus
webmentions, now page, and everything else in between.
</p>
<p>
He has built
<a href="https://echo.rknight.me/" class="notion-text-href">echo</a> that can
read RSS feeds and write that data to various places with webhooks. For
example, he uses that to pull in his movie reviews from Letterboxd into
<a href="https://rknight.me/almanac/" class="notion-text-href">his website</a>. Echo then picks that change up and posts it to Mastodon which is pretty
cool.
</p>
<p>
Robb goes through a couple of more examples like that in the talk as well, I
highly recommend watching the talk and adding Robb’s blog to your RSS reader.
</p>
<h2 class="notion-heading_2 notion-color-default">Next talk, maybe by you?</h2>
<p>
THE Eleventy Meetup is always looking for speakers. They have
<a href="https://11tymeetup.dev/cfp/" class="notion-text-href">an always-open call for proposals</a>
and are very welcoming to first-time speakers. The community is lovely and
very supporting and it would be cool to see more talks about Eleventy topics.
</p>
<p>
If you’re not quite sure what you could talk about, I recently wrote
<a href="https://hamatti.org/posts/talk-ideas-for-new-and-experienced-speakers/" class="notion-text-href">a blog post with some ideas for talk topics</a>.
</p>
Pull request is my proposal for changes
2024-02-24T00:00:00Z
https://hamatti.org/posts/pull-request-is-my-proposal-for-changes/
<p>
<a href="https://hachyderm.io/@ellie/111821537978260991" class="notion-text-href">Ellie Huxtable asked in Mastodon</a>
recently (emphasis mine):
</p>
<blockquote class="notion-quote notion-color-default">
<b class="notion-text-bold">How would you feel if a maintainer pushed a formatting change/simple lint
fix to your PR before merging it?</b>
Often I receive PRs that I'm happy to approve, but they just need one tiny
tweak. I've always felt like it's rude to just push to someone's branch, but
it would improve merge times by a bunch
</blockquote>
<p>
I’ve always been of the school of thought that treats pull requests (and any
code really) as “our code” rather than “my code” - for varying variables of
“us”.
</p>
<p>
When I make a pull request, whether it is for a personal project, an internal
team repository or in open source, I treat it as a
<b class="notion-text-bold">proposal for change.</b> To me, that means that
I’ve given up on any kind of exclusiveness of “it’s this or nothing” or “I’m
the only one who can change it”. It becomes part of the project and depending
on the project and my role in it, it may be myself, my teammates or an open
source maintainer (or some combination of these) that eventually decides what
gets merged in.
</p>
<p>
To the original question, I’m 100% up for the maintainer doing formatting
changes and lint fixes to my pull requests. I’d go even further though: I’m
good for you making any changes that would make it a better set of changes.
Sometimes it’s by asking me to change things and sometimes by making changes
yourself.
</p>
<p>To me, that makes no difference.</p>
Talk ideas for new and experienced speakers
2024-02-21T00:00:00Z
https://hamatti.org/posts/talk-ideas-for-new-and-experienced-speakers/
<p>
Are you thinking about giving a talk in your local meetup or sending proposals
to conferences but you’re not quite sure what you could talk about? In this
article, I share a bunch of high level categories with real talk examples to
get your creativity flowing.
</p>
<p>
If you’re a beginner in the industry and want some inspiration and
encouragement, Marijke has a great blog post
<a href="https://marijkeluttekes.dev/blog/articles/2024/02/08/you-can-be-a-beginner-and-also-a-speaker-blogger-or-participant/" class="notion-text-href">You can be a beginner and also a speaker, blogger, or participant</a>.
</p>
<p>
<b class="notion-text-bold">Terminology:</b> I use the word
<i class="notion-text-italic">technology</i> to mean nearly anything in the
tech space. It could be a programming language (like Python or Java), a
library (like datasette or pytest) or a tool (like databases or code editors)
or even something else.
</p>
<h2 class="notion-heading_2 notion-color-default">
Your talk doesn’t have to be about “best practices”
</h2>
<p>
Often, we craft talks our talks as clean, happy path, best practices talk.
Kinda like our tutorials and documentation. Sometimes that works. In real
life, we’re very rarely in a situation where we’d be able to bring in a tool
or library or new way of working into a clean greenfield project. Usually we
have a messy situation with a lot of real-life restrictions, legacy code and
limited time to be perfect.
</p>
<p>
Talks don’t have to be “best practices” kinds of talks. Real-life examples,
where you share the struggles and issues and realities, are almost always more
interesting than generalised ones.
</p>
<h2 class="notion-heading_2 notion-color-default">Talk topic ideas</h2>
<h3>Technology 101</h3>
<p>
Introduce the audience to a topic in a tutorial way: what is this technology,
what is it used for, how to install it and a few examples for how to use it.
In my opinion, this can be the easiest one for a new speaker to start with
because you can rely a lot on the documentation of the technology to find
examples.
</p>
<p>
<b class="notion-text-bold">Example: </b><a href="https://www.youtube.com/watch?v=ZFLOMSuWHxg" class="notion-text-href">Alex Ware - "An Introduction to PySpark"</a>. Alex’s PyCon AU talk about PySpark is a good example of how to introduce a
technology. She talks about what the technology is, how it compares to other
technologies used for similar problems and how to use it with an example
dataset.
</p>
<h3>Use X to achieve Y</h3>
<ol class="notion-numbered_list">
<li class="notion-numbered_list_item notion-color-default">
Pick a technology you are familiar with
</li>
<li class="notion-numbered_list_item notion-color-default">
Pick a problem or a case where technology can be used to solve it
</li>
</ol>
<p>
Give a quick intro to the technology X, to the problem/case Y and then talk
about how X is used to achieve Y. I find this type of talk more effective and
interesting than a pure 101 talk because it gives me ideas for how to use the
technology in real life.
</p>
<p>
<b class="notion-text-bold">Example: </b><a href="https://youtu.be/EitSgL0HY6M?si=0LDj82BfYIsWYZsR" class="notion-text-href">Olavi Haapala: Using prefetch and preload to speed up your site</a>. In this talk, Olavi goes through how assets are loaded by the browser, why
these two features are good for performance and how to use them in practice.
</p>
<h3>Project showcase</h3>
<p>
Did you build something that you can show? A hobby project, an open source
tool or something at work that you got permission to talk about and show some
code. Talk about it!
</p>
<p>
Make the talk about the solution and the code rather than marketing your
product. If the product is interesting to the audience, they’ll check it out
even if you’re not pushing it to their face.
</p>
<p>
<b class="notion-text-bold">Example: </b><a href="https://www.youtube.com/watch?v=0R3FO666Jzk" class="notion-text-href">Penelope Phippen: Building Rubyfmt</a>. Penelope shared her experience from building Rubyfmt tool in 2020’s NoRuKo
virtual conference.
</p>
<h3>Tools for developers</h3>
<p>
Let’s say you’re preparing to give a talk in a Python meetup. You can talk
more about than just stricly Python programming language topics. Developers
use a lot of tools, some specific to a language and some generic, and sharing
tips and ideas for how to use those tools makes a great talk.
</p>
<p>
You can talk about command line tools, editors, productivity tools or
something else that you use in your day-to-day development.
</p>
<p>
<b class="notion-text-bold">Example: </b><a href="https://youtu.be/aolI_Rz0ZqY" class="notion-text-href">Scott Chacon: So you think you know git</a>. In this talk from FOSDEM 2024, Scott goes through features in Git that you
might not know. These days, almost every developer uses version control and
this shotgun buffet style talk likely has something to learn for everyone.
</p>
<h3>X for Y developers</h3>
<p>
Do you know something that’s related to the event’s theme? Talk about that
topic within the context of the theme of the event.
</p>
<p>
Make the talk practical and try to find a balance for what content and context
is needed. You don’t need to dive deep into the topic itself. Focus on what is
usable information to your audience.
</p>
<p>
It’s okay if at the end of your talk, the audience has gaps in their knowledge
of the topic itself. X for Y talks work best when everyone leaves with one new
practical idea and knowledge of other things existing so they can learn more
if needed.
</p>
<p>
<b class="notion-text-bold">Example: </b><a href="https://www.youtube.com/watch?v=cajn_hqQiG4" class="notion-text-href">Richard Yen: How to Ride Elephants Safely Working with PostgreSQL when your
DBA is not around</a><b class="notion-text-bold"> </b>is all about practical knowledge about
monitoring and recovering your PostgreSQL database in an emergency, aimed for
Python/Django developers.
</p>
<h3>Supplementary topics to writing code</h3>
<p>
I always struggle to find a good name for this category. Talk about topics
like testing, documentation, accessibility, debugging, processes, learning,
mentoring, community, open source, blogging, usability and so on.
</p>
<p>
<b class="notion-text-bold">Example:</b>
<a href="https://hamatti.org/talks/contemporary-documentation" class="notion-text-href">Juha-Matti Santala: Contemporary Documentation</a>
is a good example of this category. Another great one is
<a href="https://www.youtube.com/watch?v=cgFtVshbAec" class="notion-text-href">Ramon Huidobro’s There's More to Open Source than Code</a>.
</p>
<h3>Using X with Y</h3>
<ol class="notion-numbered_list">
<li class="notion-numbered_list_item notion-color-default">Pick one tool</li>
<li class="notion-numbered_list_item notion-color-default">
Pick another tool
</li>
</ol>
<p>
Talk about how they work together. “htmx with Django” and “Firebase with
React” are combinations you could see in talks. Integrating different tools
with each other can be a bit of a pain sometimes so you showing an example of
how to do it can do wonders to inspire others to start learning and using
those tools.
</p>
<p>
<b class="notion-text-bold">Example:</b>
<a href="https://youtu.be/16rKyUZuttE" class="notion-text-href">Eric Tollerud: Vue + Django: Combining Django Templates and Vue Single File
Components without compromise</a>. This is a textbook example of a talk integrating two technologies.
</p>
<h3>What’s new in X?</h3>
<p>
Did your favorite tool get new cool features in the latest update? Share them
with examples and reasons for why you think they are useful or fun.
</p>
<p>
While these are all things audience could read from changelogs, people tend
not to read them for all the tools and we miss on learning new things that
different tools bring.
</p>
<p>
<b class="notion-text-bold">Example:</b>
<a href="https://youtu.be/lPl5Q5gv9G8" class="notion-text-href">Sarah Boyce: What’s new in Django 5.0</a>. This example is not a talk delivered in a conference but it very well could
be.
</p>
<h3>A cross-section of X and Y</h3>
<p>
When I started writing my bachelor’s thesis, the most common advice I got for
picking the topic was: “Choose two things and write about how they interact”.
This is slightly similar to <b class="notion-text-bold">Using X with Y </b>and
<b class="notion-text-bold">Use X to achieve Y</b>.
</p>
<p>
I recently helped a new speaker to brainstorm ideas for his talk and we
discovered an interesting cross-section of design systems and Web Components
in his work and I’m hoping to see that talk soon.
</p>
<h3>Did you recently fix a bug?</h3>
<p>
If you encountered a bug that wasn’t trivial to fix, share with the audience
how it happened. Talk about the problem and how it manifested, what was your
approach to debugging, what resources you found and learned from and how you
finally fixed the issue.
</p>
<p>
<b class="notion-text-bold">Example:</b>
<a href="https://www.youtube.com/watch?v=VPldDxuJDsg" class="notion-text-href">Ryan Cheley: Contributing to Django or how I learned to stop worrying and
just try to fix an ORM Bug</a>. In this talk, Cheley talks through his experience making his first Django
contribution and the support he received from the community.
</p>
<h3>Stories from projects of the past</h3>
<p>
While learning practical things is fun, sometimes the best talks are similar
to stories shared at campfires or pubs. Especially if you’ve been in the
industry for a long time, share some of those stories from throughout the
years.
</p>
<h3>More ideas</h3>
<p>
For more ideas and a nice printable worksheet to work on your idea, check out
<a href="https://lucybain.com/blog/2016/conference-proposal-ideas/" class="notion-text-href">Lucy Bain’s blog post Conference proposal ideas</a>.
</p>
<p></p>
Thank you, Startuplifers
2024-02-19T00:00:00Z
https://hamatti.org/posts/thank-you-startuplifers/
<p>
January 2024 marked the end of a fantastic program called
<a href="https://startuplifers.org/" class="notion-text-href">Startuplifers</a>
(formerly known as Startup Life, hence my use of that name later in the post):
</p>
<blockquote class="notion-quote notion-color-default">
Today marks the end of a remarkable era. Startuplifers bid farewell to more
than 10 years of supporting brilliant talent to reach Silicon Valley and other
startup hubs worldwide. Unfortunately, the Startuplifers organization will be
sunsetting its operations in the next few weeks.
</blockquote>
<p>
I was born too late to explore the world and born too early to explore the
universe but I was born at the right time to become a Startuplifer. And for
that, I’m very thankful.
</p>
<h2 class="notion-heading_2 notion-color-default">
Let’s start from the beginning
</h2>
<p>
To understand its impact to my life, we need to rewind more than a decade, to
early 2013.
</p>
<p>
When 2012 turned into 2013, I was a student of computer science and statistics
who didn’t really have high hopes or expectations for the future. And it
wasn’t because my view of future was somehow dark or grim, I just didn’t see
myself doing anything meaningful in life.
</p>
<p>
One day, I was studying with fellow students at the basement of our computer
science department when an engineering teacher called Ville poked his head
into the room and very briefly mentioned something about a Capstone project
and Fudan University in Shanghai and that moment sparked a tiny seed of
thought in my mind.
</p>
<p>
I ended up sending an application to this project despite not really matching
any of the requirements and by some sheer luck, I got accepted. I spent the
most part of the year with a wonderful interdisciplinary team and gained a lot
of great experiences.
</p>
<p>
That also led to me being introduced to a local student startup community
Boost Turku at which point I had already gotten a spark of a dream to one day
work in Silicon Valley. That community threw gasoline to the spark and got me
even more excited.
</p>
<p>
And then, one day in the spring of 2013, we traveled to Aalto University with
our project team and after a day of activities there, I sat at a corner of
Startup Sauna, brainstorming a job application because I had gotten the idea
to apply for an internship at Codecademy.
</p>
<p>
And at that moment, everything changed. Everything I’ve done since April of
2013 pretty much was caused by that moment.
</p>
<h2 class="notion-heading_2 notion-color-default">
Where a boy meets a future
</h2>
<p>
This guy, Mikko, who I had met years before at The Union of Upper Secondary
School Students in Finland, walked past and asked what I was doing. I told him
I was making a job application to Codecademy. He told me to hang around as he
was going to a meeting but we could grab a beer afterwards.
</p>
<p>
As we sat drinking that beer, we talked about it. Not only did Mikko have a
friend working at Codecademy at that point, he was also running a program
called Startup Life which sent engineering, design and business students to
startups in Silicon Valley to do internships and learn about startups and the
culture of startups.
</p>
<p>
I ended up getting an interview with Codecademy but it didn’t turn into a job.
I thought about Startup Life a lot during the summer and gave it a proper go
in their fall batch.
</p>
<h3>The booklet</h3>
<img src="https://hamatti.org/assets/img/posts/thank-you-startuplifers/1.png" alt="On the left, a front cover of Startup Life booklet with an orange background, Startup Life logo and text “No costs. No hassle. Interns who build and ship.” On the right, the first pages of the booklet explaining the program and featuring Jori Lallo as an alumni. " />
<p>
During that spring I found this booklet. I can’t remember if Mikko gave it to
me or if I picked it up myself from Startup Sauna but as you can see from the
photos, it has lived a life during these almost 11 years.
</p>
<p>
In that booklet, they introduced the program and five alumni. While I didn’t
truly believe it to be possible at the time, I kinda wanted to become like
them.
</p>
<h2 class="notion-heading_2 notion-color-default">
My Startup Life experience
</h2>
<p>
I applied to a couple of positions through the program and after failing a few
interviews with other companies, on 21st of October 2013 I received a message
from a startup called Chartio that they were interested in interviewing me.
Things moved real fast. On 23rd, I had my first (and only) interview at 20:30
and the day later, I received an offer from them.
</p>
<p>
It took a few months to get all the visa stuff sorted but on January 2nd,
2014, I landed to the San Francisco International airport and was ready to
start my adventure.
</p>
<img src="https://hamatti.org/assets/img/posts/thank-you-startuplifers/2.png" alt="Rooftops of northern San Francisco with Golden Gate Bridge in the distance on a clear morning with a few clouds in the horizon. " />
<p>
On my first morning, I woke up, went to the rooftop of the place I was staying
at and took this photo. It was mind-blowing to wake up to the sight of Golden
Gate Bridge.
</p>
<img src="https://hamatti.org/assets/img/posts/thank-you-startuplifers/3.png" alt="Photo of Juhis and text “Juha-Matti Santala, engineering intern, suomalainen” " />
<p>
When I started, we were a team of 11 people and when I left around Christmas
when my visa was about to expire, we had grown to around 25 people. I learned
so much from working there. Not only about software development but about
company culture, the importance of great team and celebrating the successes.
</p>
<img src="https://hamatti.org/assets/img/posts/thank-you-startuplifers/4.png" alt="A group photo of 20 people standing on a karting podium. " />
<p>
I’m forever grateful for Dave, Justin, Nathan, Paul, Praveen, Peter, Jess,
Steven, Melissa, Natasha, Jen, Ben, Tara and all the others at Chartio for the
year that turned my life around.
</p>
<p>
As I was writing this, I discovered that despite the company being sold to
Atlassian since, my old blog posts that I wrote for the company blog are still
up. I wrote about
<a href="https://chartio.com/blog/finnterning-at-chartio/" class="notion-text-href">Finnterning at Chartio</a>
and
<a href="https://chartio.com/blog/the-beautiful-game/" class="notion-text-href">how I used the product to create visualizations of the 2014 FIFA World
Cup</a>.
</p>
<p>
The Startup Life community was amazing out there. Moving to the other side of
the planet while not knowing anyone there isn’t the easiest thing to do.
Luckily, we had a group of Startup Life alumni, other Finns who lived their
and their new local friends to hang out with. I never felt alone there,
despite a patch of home sickness around the summer.
</p>
<h2 class="notion-heading_2 notion-color-default">After coming home</h2>
<p>
For a while, after I had moved back home, the alumni community was such a
lifeline for me. I had quite a big trouble adjusting back home and suffered
from a reverse culture shock and it was so helpful to have fellow alumni who I
could chat with.
</p>
<p>
I ended up working with some of the new friends I had made there and I became
one of the alumni I had looked up to in the booklet I mentioned earlier.
</p>
<p>
I wanted to help others do the same, so I ended up doing a ton of talks and
presentations in universities and student events about my experience. Through
that, I also got to make more friends from the changing teams of organizers of
the program and all the new alumni who went through the experience.
</p>
<p>
I wouldn’t be where I am now if it wasn’t for that one moment in the spring of
2013 and the year that followed from there.
</p>
<p>Thank you, Startuplifers ❤️</p>
Syntax Error #12: Browser Extension Debugging
2024-02-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-12-debugging-browser-extensions/
<p>
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this 1 year anniversary birthday issue of
Syntax Error I write about tips and tools for debugging browser extensions.
Read full article at
<a href="https://www.syntaxerror.tech/syntax-error-12-browser-extension-debugging/">syntaxerror.tech/syntax-error-12-browser-extension-debugging/</a>
and either subscribe to the email or RSS feed to catch all of them.
</p>
We organized a Code in the Dark event
2024-02-16T00:00:00Z
https://hamatti.org/posts/we-organized-code-in-the-dark-event/
<p>
One of the most fun events I’ve been involved with is
<a href="http://codeinthedark.com/" class="notion-text-href">Code in the Dark</a>. We
<a href="https://hamatti.org/posts/code-in-the-dark-turku/" class="notion-text-href">organized one with our Turku ❤️ Frontend community back in 2017</a>
and ever since, every now and then it’s popped up in discussions with people.
As I’ve lived in Helsinki and Berlin for most of the time since then, it’s
been on my back-burner. Now that I moved back home to Turku a year ago,
organizing another Code in the Dark was high on my priority list.
</p>
<p>
This Thursday, with the
<a href="https://turkufrontend.fi/" class="notion-text-href">Turku ❤️ Frontend</a>
people, we finally got back together to practice our frontend development
craft and see what funny things come out from the keyboards of our
participants.
</p>
<h2 class="notion-heading_2 notion-color-default">
Code in the Dark in a nutshell
</h2>
<p>
Code in the Dark is a “competition” where teams of 2-3 developers work on a
group of challenges. On each challenge, teams get a screenshot of a website
and an editor where they can write their HTML and CSS. The unique twist is
that they are working “in the dark”, meaning they are not allowed to preview
the website during the 15 minute time slot for each challenge.
</p>
<p>
At the end of the challenge, all built websites are showed to the audience who
gets to vote for a winner on each round. Simultaneously, it challenges
developers for their core HTML and CSS skills while creating very funny cases
where one tiny mistake can completely blow up the layout and make a funny
looking result. Having fun is all this event is about.
</p>
<h2 class="notion-heading_2 notion-color-default">
We have the loveliest community
</h2>
<p>
I love being part of our community as events like these really brings people
together.
</p>
<img src="https://hamatti.org/assets/img/posts/we-organized-code-in-the-dark-event/1.png" alt="Matias and Aarni standing on the side of the main stage, working on their laptops. " />
<p>
Our tooling team <b class="notion-text-bold">Aarni </b>and<b class="notion-text-bold">
Matias</b>
worked on
<a href="https://github.com/valohai/citadel" class="notion-text-href">citadel</a>
(Code in the Dark event management tool built with Django) and
<a href="https://github.com/Matsuuu/code-in-the-dim" class="notion-text-href">code-in-the-dim</a>
(an online code editor for Code in the Dark events) to have a wonderful
environment for the both the participating developers and the audience in the
attendance.
</p>
<p>
Having such a great duo build an in-house tooling is fantastic because it
meant we could have the features we needed (and they were able to fix a bug we
encountered live during the event. What would a tech event be without some
demo effects, right?
</p>
<p>
Our challenge team <b class="notion-text-bold">Ismail </b>and<b class="notion-text-bold">
Emad</b>
did a great job with selecting a series of websites with escalating difficulty
level for the event. During the evening, our teams had to replicate front
pages of DuckDuckGo, Turku ❤️ Frontend, Notion and Revolut.
</p>
<p>
And a big thank you goes to our local partners
<b class="notion-text-bold"><a href="https://kipina.fi/" class="notion-text-href">Kipinä Software</a></b>
(providing food and drinks for everyone participating),
<b class="notion-text-bold"><a href="https://arado.fi/" class="notion-text-href">Arado</a></b>
(providing screens for the event) and
<b class="notion-text-bold"><a href="https://sparkup.businessturku.fi/en/" class="notion-text-href">SparkUp</a></b>
(providing us the space to host the event) without who this event would not
have been possible.
</p>
<p>
Finally, a thank you to all the four teams – Team Winner, Taulukkotaiturit,
Don’t drink and root and Pirjot – who participated and everyone in the
community who helped out set up the space and clean up afterwards.
</p>
<h2 class="notion-heading_2 notion-color-default">The Event</h2>
<p>
We kicked off the event with pizza, snacks, drinks and catching up with old
friends and making new ones. Once we got everything set up, we ran two rounds
of challenges, followed by a break for more food and drinks and socializing,
followed by another two rounds and then more hanging out.
</p>
<img src="https://hamatti.org/assets/img/posts/we-organized-code-in-the-dark-event/2.png" alt="Four teams sitting behind desks. The desks have displays that show their code editors. Behind them, a large screen that shows four previews of Turku Frontend websites the teams are building. " />
<p>
During our four rounds, each team had 15 minutes to recreate a website based
on a screenshot they were provided. The teams only had access to a web-based
code editor but the audience could see their code (on displays on teams’
desks) and the live preview of each site (on the big screen) during the event.
</p>
<p>
What I really like about the event is that you get to see how different teams
approach the same problem with very different approaches. Some start by laying
out the layout and broad strokes while others start with more detail-level
things working their way through from top to bottom.
</p>
<img src="https://hamatti.org/assets/img/posts/we-organized-code-in-the-dark-event/3.png" alt="Four teams sitting behind desks. The desks have displays that show their code editors. Behind them, a large screen that shows four previews of DuckDuckGo’s front pages the teams are replicating. " />
<p>
With 15 minutes, there’s not enough time to do everything so another debate is
where do you make your tradeoffs. As the winner of each round is voted by the
audience, there’s no right or wrong approach.
</p>
<p>
The most important thing though is not to win but to have a great time and
often the small mistakes leading to silly solutions are the best ones because
we all get a good laugh. It’s important to remember that we laugh with the
mistakes, not at them.
</p>
<p>
A winning team of each round got a small prize provided by Kipinä and we all
learned a thing or two along the way.
</p>
<img src="https://hamatti.org/assets/img/posts/we-organized-code-in-the-dark-event/4.png" alt="A group photo of 17 people in two rows: the participants, organizers and sponsors of Code in the Dark. Behind them, a large screen with Turku Frontend website that says “Welcome to Turku ❤️ Frontend” " />
<hr class="notion-divider" />
<p>
<i class="notion-text-italic">Photos in this blog post are from </i><i class="notion-text-italic"><a href="https://masto.ai/@tero/111936225115817341" class="notion-text-href">Tero Ykspetäjä</a></i><i class="notion-text-italic"> and Veli-Pekka Virtanen</i>
</p>
<p></p>
Stickers tell stories
2024-02-14T00:00:00Z
https://hamatti.org/posts/stickers-tell-stories/
<p>
I’m one of the people whose laptop covers are full of stickers. Recently, a
friend shared a picture of his laptop and noted (paraphrased translation):
</p>
<blockquote class="notion-quote notion-color-default">
"Looking at them, I realized how many of these are connected to stories and
people”
</blockquote>
<p>
That’s why I love my stickers. They tell a bit about myself to others but more
importantly, they remind me of the stories and people connected to them. Every
time I take out my laptop, I catch a glimpse of a sticker and warm feelings
flow in.
</p>
<p>Here’s my current laptop and some of the stories the stickers tell:</p>
<img src="https://hamatti.org/assets/img/posts/stickers-tell-stories/1.png" alt=" " />
<p>
There are a lot of different stickers, some overlapping each other as time has
passed. The stories told are temporal and some stickers invoke stronger
memories and others are just there because they are funny or cute. Or because
someone lovely gave them to me.
</p>
<p>Let me share some of those stories and stickers with you.</p>
<h2 class="notion-heading_2 notion-color-default">
Finnish tech podcasts: Webbidevaus.fi & Koodia Pinnan Alla
</h2>
<img src="https://hamatti.org/assets/img/posts/stickers-tell-stories/2.png" alt=" " />
<p>
<a href="http://webbidevaus.fi/" class="notion-text-href">Webbidevaus.fi</a>
is a Finnish tech podcast
<a href="https://webbidevaus.kapselistudio.net/56" class="notion-text-href">I visited</a>
eons ago in September of 2019 to chat with Antti ja Riku about documentation,
developer communities and livecoding in Twitch. It’s one of the most visually
stunning and glorious of the stickers on my lid.
</p>
<p>
<a href="https://koodiapinnanalla.fi/" class="notion-text-href">Koodia Pinnan Alla</a>
is another Finnish tech podcast that I haven’t visited (yet) but am a big fan
and a happy listener. They have great in-depth discussions of technical
concepts.
</p>
<h2 class="notion-heading_2 notion-color-default">Futurice stickers</h2>
<img src="https://hamatti.org/assets/img/posts/stickers-tell-stories/3.png" alt=" " />
<p>
I worked for almost 4,5 years at
<a href="https://futurice.com/" class="notion-text-href">Futurice</a> as a
developer and developer advocate.
<b class="notion-text-bold">Global Code Camp 2019 </b>was an internal
hackathon me and my team organized in Tallinn in 2019 with almost 40
developers and designers from our offices across Europe joining.
<b class="notion-text-bold">Chilicorn</b> is the logo of the company’s social
responsibility and open source program.
<b class="notion-text-bold">The Rise of Futulon</b> sticker is for our annual
company all-hands, organized in Tampere in 2019. Hidden behind a bunch of
other stickers is also a sticker for our lottery lunch concept where employees
were assigned to their colleagues for lunch.
<b class="notion-text-bold">Tech Weeklies </b>was the weekly tech meetup I ran
for a couple of years every Friday. The two
<b class="notion-text-bold">Hacktoberfest </b>stickers are also from my time
at Futurice where in October, we brought the local community together to work
on open source projects.
</p>
<h2 class="notion-heading_2 notion-color-default">Conferences and meetups</h2>
<img src="https://hamatti.org/assets/img/posts/stickers-tell-stories/4.png" alt=" " />
<p>
<b class="notion-text-bold"><a href="https://turkufrontend.fi/" class="notion-text-href">Turku ❤️ Frontend</a></b>
is a developer community I’ve been running since 2015 and the one thing in
life I’m most proud of. <b class="notion-text-bold">TurkuSec </b>is another
local meetup I’m happy to be a member of.
<b class="notion-text-bold"><a href="https://futurefrontend.com/" class="notion-text-href">Future Frontend</a></b>
and <b class="notion-text-bold">React Finland</b> are conferences I’ve been
involved in organizing.
<b class="notion-text-bold"><a href="https://djangoday.dk/" class="notion-text-href">Django Day Copenhagen</a></b>
and
<b class="notion-text-bold"><a href="https://cz.pycon.org/" class="notion-text-href">PyCon CZ</a></b><b class="notion-text-bold"> </b>I’ve visited as a speaker.
<b class="notion-text-bold">Koodiklinikka</b> is a Finnish developer community
where I’m an admin in the Slack group and run annual salary surveys. A small
<b class="notion-text-bold">Euruko</b> sticker reminds me of the good times
with Ruby developers.
</p>
<h2 class="notion-heading_2 notion-color-default">Syntax Error</h2>
<img src="https://hamatti.org/assets/img/posts/stickers-tell-stories/5.png" alt=" " />
<p>
<b class="notion-text-bold"><a href="https://syntaxerror.tech/" class="notion-text-href">Syntax Error</a></b>
is a tech newsletter about debugging that I write monthly to help developers
learn tools and techniques to figure out what’s wrong with their code. You can
read it on the website or subscribe via email or RSS to make sure you don’t
miss any.
</p>
<h2 class="notion-heading_2 notion-color-default">Mozilla & Firefox</h2>
<img src="https://hamatti.org/assets/img/posts/stickers-tell-stories/6.png" alt=" " />
<p>
To celebrate my short stint at Mozilla, I have a Firefox sticker and a Mozilla
Remote sticker at the corners. Firefox is also my main browser in day-to-day
use and it’s always nice to passively remind people it’s an option.
</p>
<p></p>
Public notes
2024-02-12T00:00:00Z
https://hamatti.org/posts/public-notes/
<p>
I have been following a mindset of
<a href="https://hamatti.org/posts/learning-in-public/" class="notion-text-href">learning in public</a>
for quite a few years now. In a nutshell, it means openly sharing what I’m
learning, what I’m building, what I’m struggling to understand and sharing the
full(-ish) process openly in my blog, social media, events and so on.
</p>
<p>
Recently, I’ve been thinking about wanting to expand it more also into
building in public. I’m only in the beginning of this because it’s even
scarier than doing something in private and then sharing the learnings in
public. What I mean by building in public is for example putting drafts for my
writing (blogs, tutorials, community announcements, event pages and so on) out
there, sharing them with the community and having a discussion way before they
get published somewhere.
</p>
<p>
It’ll still take time to really be able to write about my experiences there.
</p>
<p>
In the meanwhile, I learned about a couple of people who have been doing
public notes.
</p>
<p>
I was watching
<a href="https://www.ryancheley.com/" class="notion-text-href">Ryan Cheley</a>’s talk
<a href="https://www.youtube.com/watch?v=VPldDxuJDsg" class="notion-text-href">Contributing to Django or how I learned to stop worrying and just try to
fix an ORM Bug from DjangoCon US 2023</a>
in which he talked about the value of writing down notes as you learn
something new. He shared that as he was researching and fixing the bug, he
kept notes in
<a href="https://github.com/ryancheley/public-notes/issues/1" class="notion-text-href">his public-notes GitHub repository</a>. I’m sure it helped him understand the things better and having a nice
record of thoughts and actions somewhere is valuable to him. But it’s also
valuable to others! It’s fascinating to me to be able to read this kind of
semi-raw stream of thoughts and questions and ideas and the discussion he’s
having with himself in those notes.
</p>
<p>
<a href="https://simonwillison.net/" class="notion-text-href">Simon Willison</a>
is another developer who keeps public notes in his GitHub repository. His
<a href="https://github.com/simonw/public-notes/issues/4" class="notion-text-href">notes of going through Jinja’s documentation</a>
are a delight to read. Simon talked about this more in his DjangoCon US talk
<a href="https://www.youtube.com/watch?v=GLkRK2rJGB0" class="notion-text-href">Increase your productivity on personal projects with comprehensive docs and
automated tests</a>
where he talks about in general why it’s a great idea to document your
single-dev hobby projects and have discussions with yourself in the issue
tracker and pull requests.
</p>
<p>
This is something I’d love to find a way to incorporate into my own flow. I do
quite often right a lot of notes as I explore new things and making them
public as I write them down would create such an interesting artifact of the
process over a longer time.
</p>
<p>
I’m not sure if in the long run, storing these in GitHub issues fit my
workflow the best but I’m willing to give it a go. Last Thursday, I bravely
started
<a href="https://github.com/Hamatti/public-notes/issues/1" class="notion-text-href">my first public note about learning PyO3</a>
and let’s be real: I really liked writing them. Especially since I struggled
in a few places. If I wasn’t writing this type of series of notes, a lot of
those experiments and failed attempts would have been lost and forgotten.
</p>
<p>
I would like to find a way to write these in a way that they would live in my
website instead of a third-party platform I cannot control but until I figure
out a great UI for that, I’ll start experimenting and seeing what I like and
how this fits my flow.
</p>
Anonymous, asynchronous friendships
2024-02-10T00:00:00Z
https://hamatti.org/posts/anonymous-asynchronous-friendships/
<p>
This month’s
<a href="https://indieweb.org/indieweb-carnival" class="notion-text-href">IndieWeb Carnival</a>
is hosted by Manuel Moreale who chose a brilliant topic of
<a href="https://manuelmoreale.com/indieweb-carnival-digital-relationships" class="notion-text-href">digital relationships</a>.
</p>
<blockquote class="notion-quote notion-color-default">
topic for the month of February is going to be “Digital relationships”. The
meaning of the topic is intentionally vague but I can think of at least three
ways to interpret it. The first is probably the most obvious: relationships
between us human beings that are lived primarily—or entirely—on the digital
world. The second is the relationship between us and the digital world itself.
The third is the growing trend of people having relationships with digital
creations such as AI fiends, boyfriends and girlfriends.
</blockquote>
<p>
By the way, if you don’t yet know Manu, I highly recommend his great blog and
especially his fantastic
<a href="https://peopleandblogs.com/" class="notion-text-href">weekly People & Blogs newsletter</a>
where each Friday he shares an interview with someone who writes a blog. I’ve
discovered so many great blogs through it.
</p>
<h2 class="notion-heading_2 notion-color-default">My dear friends</h2>
<p>Some of my best friendships are with people I have never met or seen.</p>
<p>
I know them by a nickname and we’ve been writing messages for two decades.
</p>
<p>
When I was young, I wasn’t very social kid. I was shy, introverted and not
like all the cool kids in the school. But somewhere in the early years of this
millennium, I got broadband Internet and joined the
<a href="https://en.wikipedia.org/wiki/IRC" class="notion-text-href">Internet Relay Chat or IRC</a>.
</p>
<p>
It was a magical place for me. You had your nickname and the discussion
happened asynchronously and anonymously. It meant that we perceived each other
through our written communication only. For a shy guy with a weird voice, it
was a safe haven of sorts. I could discuss and argue with others and I was
treated based on the merits of my thoughts and communication and not by my
looks or my background.
</p>
<p>
A written, asynchronous communication allowed me to think about things and
craft my messages with more care and I didn’t have to think on my feet in
situations that normally would have been intimidating.
</p>
<p>
Same happened with discussion forums. I learned to become quite a writer by
spending countless hours on different chat rooms and forums discussing topics
that I cared about.
</p>
<p>
In those discussions so many great, life-long friendships were forged. Some of
my dearest friends these days are people I’ve met online – and some of them I
still haven’t met in person. I don’t necessarily know what they look like or
what their real names are. But I consider them great friends because we talk
daily or at least weekly online.
</p>
<p>
Others, I’ve gotten to know in real life. And it’s a wonderful sensation when
you meet someone for the first time in a pub and the usual “getting to know a
stranger” feeling isn’t there at all. We would continue the discussions we’ve
had for years without a hitch.
</p>
<p>
Over the years, I’ve grown more socially confident and become a bit of a
people person to my own surprise. I’ve also noticed that the diversity of my
friend group has diminished through that. In real life, I end up in
discussions with people who are more similar to me. Usually technology
oriented, or maybe interested in Pokemon or learning and quite often my age.
</p>
<p>
In IRC and the forums, I became friends with people I would have never ended
up in the same discussion with in the real life. Some were much older than me,
some younger. People who lived in different parts of the country or world and
ended up working in completely different fields and living a very different
life than me.
</p>
<p>I kinda miss that.</p>
<p>
My life these days are much more focused on professional interests. The online
spaces I spend time in are usually related to software development or
community building. And don’t get me wrong, I love hanging out with people who
share my passions. And as an adult, I’m getting more out of the international
online friendships too since I always have some friends to hang out with, no
matter where I travel in the world. But I’m less likely to become good friends
with an artist, an actor or a health care worker than I used to be in back in
the day.
</p>
<h2>Other submissions</h2>
<p>
You can find all the other 44 submissions in Manu's
<a href="https://manuelmoreale.com/indieweb-carnival-roundup">wonderful roundup post</a>.
</p>
Highlight Mastodon posts by hashtag with Stylus and :has
2024-02-07T00:00:00Z
https://hamatti.org/posts/highlight-mastodon-posts-by-hashtag-with-stylus-and-has/
<p>
I like that Mastodon provides a way to follow hashtags. It’s a great way to
find new people to follow and new things to read or watch for given topic. But
I wish there was a clearer indicator for which posts are hashtagged and which
are from people I follow.
</p>
<p>
This is because I put more value on those that I follow so I read them with
more thought and just glance over the ones that come from a hashtag.
</p>
<p>
Now, this solution isn’t perfect because it does not separate
<b class="notion-text-bold">followed accounts who used the hashtag </b>from
<b class="notion-text-bold">non-followed accounts whose post is on my feed because I follow the
hashtag. </b>But it’s a smaller issue for me personally so I haven’t bothered to do
anything about it for now.
</p>
<h2 class="notion-heading_2 notion-color-default">The power of custom CSS</h2>
<p>
Using
<a href="https://addons.mozilla.org/en-US/firefox/addon/styl-us/" class="notion-text-href">Stylus Firefox extension</a>
and the power of the new
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has" class="notion-text-href">:has pseudo-class</a>, I added a visual indicator to my feed when using the browser.
</p>
<p>
Each post in Mastodon is an <code class="notion-text-code">article</code> and
each hashtag is a link
<code class="notion-text-code"><a href="/tags/[hashtag]/"></code>.
Thanks to :has, we can now target articles that have a specific hashtag:
</p>
<pre class="language-css"><code class="language-css">article:has(a[href="/tags/python" i]) {
border: 1px solid orange;
}</code></pre>
<p>Let’s walk through this selector:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">article</code> matches every
<code class="notion-text-code"><article></code> element in the page
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">:has()</code> pseudo-class filters the
articles down to ones that have at least one child element that matches
what’s inside there
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">a</code> matches every
<code class="notion-text-code"><a></code> element
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">[href="/tags/python"]</code> filters those
anchor tags down to ones that have
<code class="notion-text-code">href</code> attribute that matches exactly
<code class="notion-text-code">/tags/python</code>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">i</code> makes
<code class="notion-text-code">href</code> matching case insensitive so it
matches <code class="notion-text-code">python</code>,
<code class="notion-text-code">Python</code> and
<code class="notion-text-code">PyThOn</code>
</li>
</ul>
<p>In the web, it looks like this:</p>
<img src="https://hamatti.org/assets/img/posts/highlight-mastodon-posts-by-hashtag-with-stylus-and-has/1.png" alt="A Mastodon post by Python Weekly with hashtags #python and #programming and a link to a Medium blog post and text “Advanced Magic Methods in Python to Customize Classes Conveniently” " />
<p>
(this example is from
<a href="https://mementomori.social/" class="notion-text-href">https://mementomori.social</a>
that uses
<a href="https://github.com/ronilaukkarinen/mastodon-bird-ui" class="notion-text-href">Mastodon Bird UI</a>)
</p>
<p>
I’ve been experimenting a bit with different approaches to add the highlight
because I want it to be easy to notice on a glance but not something that
steals the focus. Right now, the slightly dimmed orange has been a good one
but I’ll adjust it as I go and eventually find something that is spot on.
</p>
I like to glue things together
2024-02-03T00:00:00Z
https://hamatti.org/posts/i-like-to-glue-things-together/
<p>
Over the holidays, I ran into a blog post
<a href="https://reprog.wordpress.com/2010/03/03/whatever-happened-to-programming/" class="notion-text-href">Whatever happened to programming?</a>
from 2010 by Mike Taylor. In it, Mike argues that development has lost its
appeal to him because it’s so much of putting together pieces other people
made and less about creating things from the scratch. He has also written
<a href="https://reprog.wordpress.com/2010/03/04/whatever-happened-to-programming-redux-it-may-not-be-as-bad-as-all-that/" class="notion-text-href">a follow-up</a>
based on the comments the original post received.
</p>
<blockquote class="notion-quote notion-color-default">
Is that <i class="notion-text-italic">programming</i>? Really? Yes, it takes
taste and discernment and experience to do well; but it doesn’t require
brilliance and it doesn’t excite. It’s not what we dreamed of as
fourteen-year-olds and trained for as eighteen-year-olds. It doesn’t get the
juices flowing. It’s not <i class="notion-text-italic">making</i>.
</blockquote>
<p>
He also quotes Donald Knuth from the book
<b class="notion-text-bold">Coders at Work</b>:
</p>
<blockquote class="notion-quote notion-color-default">
“The problem is that coding isn’t fun if all you can do is call things out of
a library, if you can’t write the library yourself. If the job of coding is
just to be finding the right combination of parameters, that does fairly
obvious things, then who’d want to go into that as a career?” (page 581)
</blockquote>
<p>
Now, I’m not gonna argue what other people should or should not enjoy. But it
was a good piece that got me thinking about my relationship with software
development so here we are.
</p>
<p>
When I want to build something (usually happens on Saturday evenings as I’m
not a professional developer but mostly a hobbyist), I’m very happy I don’t
have to build the building blocks but I get to install the frameworks and
libraries I enjoy and get a running start.
</p>
<p>
With a quick <code class="notion-text-code">django-admin startproject</code> I
get to run with a good bunch of tooling and boilerplate written by people much
smarter than me. I don’t have to think about how to build templating engines
or ORMs and migrations. I get to write code that is specific to my current
needs.
</p>
<p>In his follow-up post, he remarks:</p>
<blockquote class="notion-quote notion-color-default">
Here’s a rule of thumb (which, like all such rules, is often broken and should
not be taken too seriously): beware of anything that calls itself a
Framework. Anything that, instead of providing stuff that you can call, takes
over the wheel and tells you what code to provide for
<i class="notion-text-italic">it</i> to call. Not always, but often, that
marks the line where this stops being fun.
</blockquote>
<p>
I’m bit 50/50 when it comes to frameworks but with good ones I do enjoy that
someone’s put thought into figuring out some subset of good practices on how
to connect pieces. I actually like the fact that frameworks guide you to write
certain type of code because it makes it easier for others to understand,
modify and extend when it follows the style and conventions.
</p>
<p>
One part of the Ruby on Rails’s The Rails Doctrine I like is
<i class="notion-text-italic"><a href="https://rubyonrails.org/doctrine#convention-over-configuration" class="notion-text-href">Convention over Configuration</a></i><i class="notion-text-italic">.</i>
</p>
<blockquote class="notion-quote notion-color-default">
Not only does the transfer of configuration to convention free us from
deliberation, it also provides a lush field to grow deeper abstractions. If we
can depend on a Person class mapping to people table, we can use that same
inflection to map an association declared as has_many :people to look for a
Person class. The power of good conventions is that they pay dividends across
a wide spectrum of use.
</blockquote>
<p>
I do empathise with Mike’s overall viewpoint though. Even I don’t enjoy
writing endless enterprise CRUD forms and reports. And I occasionally enjoy
writing tools from the first principles. But I’m definitely more of the type
of developer who cares about the outcome than if I built all the pieces to get
there.
</p>
We have a great developer community scene in Turku
2024-01-31T00:00:00Z
https://hamatti.org/posts/we-have-a-great-developer-community-scene-in-turku/
<p>
This year looks better than in a long time for software developers in Turku
who want to spend time together and meet other like-minded developers. There
are almost a dozen great communities active right now.
</p>
<p>Let’s take a look at what’s happening in Turku.</p>
<h2 class="notion-heading_2 notion-color-default">Turku ❤️ Frontend</h2>
<p>
<a href="https://turkufrontend.fi/" class="notion-text-href">Turku ❤️ Frontend</a>
is a community for people interested in frontend web development. We welcome
everyone who’s interested in the topic: whether you’re a student, hobbyist,
junior or senior developer, we are happy to have you join our events. We
organize monthly meetups on the last Wednesday of the month and occasionally
other types of events like Code in teh Dark events, hack days or other events
to bring the community together.
</p>
<h2 class="notion-heading_2 notion-color-default">Turku WordPress meetup</h2>
<p>
<a href="https://www.meetup.com/turku-wordpress-meetup/" class="notion-text-href">Turku WordPress meetup</a>
brings together developers and users of WordPress to learn more about the
platform, developing on it and has historically had a wide variety of style in
events and topics over the years. It’s a great community to find likeminded
people who are working on the same technology as you.
</p>
<h2 class="notion-heading_2 notion-color-default">TurkuSec</h2>
<p>
Next on the list is
<a href="https://turkusec.fi/" class="notion-text-href">TurkuSec</a> for all
the information security enthusiasts in the area. Deep tech, interesting
stories and a very welcoming community make TurkuSec a fantastic choice of a
community to join. They also stream their events
<a href="https://www.twitch.tv/turkusec" class="notion-text-href">in Twitch</a>
if you can’t make it to the event in person. They are also part of a larger
<a href="https://citysec.fi/" class="notion-text-href">CitySec movement</a> in
the Nordics and Baltics.
</p>
<h2 class="notion-heading_2 notion-color-default">IGDA Finland Turku Hub</h2>
<p>
Game developers find their home in
<a href="https://fi-fi.facebook.com/groups/338541632908779/" class="notion-text-href">the local IGDA group</a>. The Turku game scene has for the longest time been a really nice community
which is really helpful for any developers looking to start and continue their
game development journeys. Peer support for game developers, opportunities to
get people to test your game and lots of great presentations to learn more
about the industry.
</p>
<h2 class="notion-heading_2 notion-color-default">Aurajoki Overflow</h2>
<p>
<a href="https://meetabit.com/communities/aurajoki-overflow" class="notion-text-href">Aurajoki Overflow</a>
has monthly meetups around different topics in software development: from
accessibility to programming languages and data to software testing. You’ll
always find something interesting to learn from their events. Each month is
dedicated to a different topic with a couple of speakers and after the meetups
the discussions often continue to the night in a local pub in relaxed company.
</p>
<h2 class="notion-heading_2 notion-color-default">Twig the Code</h2>
<p>
<a href="https://twigthecode.com/" class="notion-text-href">Twig the Code</a>
is a group of women in tech who come together to learn from each other and
work on projects together. They organize meetups, workshops, company visits
and other events to encourage more women to the software industry.
</p>
<h2 class="notion-heading_2 notion-color-default">CNFC & Kubernetes Turku</h2>
<p>
Interested in cloud and Kubernets?
<a href="https://www.meetup.com/Kubernetes-Turku/" class="notion-text-href">Kubernetes Turku</a>
is a community for you. Join their meetup.com group and you’ll get the info
for new events to join when they are organized.
</p>
<h2 class="notion-heading_2 notion-color-default">Turku.ai</h2>
<p>
The artificial intelligence and machine learning people gather together at
<a href="https://www.meetup.com/turku-ai/" class="notion-text-href">Turku.ai</a>’s activities. Their events cover both academic cutting edge research and
practical industry implementations with great speakers and a nice community of
likeminded people.
</p>
<h2 class="notion-heading_2 notion-color-default">Archipylago</h2>
<p>
One of the new communities in the area is
<a href="https://archipylago.dev/" class="notion-text-href">archipylago</a>
(run by me and my mate Dan) that gathers together Python developers from all
walks of the industry. With Python being used for in many ways to solve
different types of problems, there’s a never-ending stream of new
opportunities to learn. On alternating months, we organize meetups with a
couple of talks and good discussions and on the other months, we organize
hands-on sprints where we get together to write code.
</p>
<h2 class="notion-heading_2 notion-color-default">TurkuMobile</h2>
<p>
Another newcomer to the scene is
<a href="https://meetabit.com/communities/turkumobile" class="notion-text-href">TurkuMobile</a>
which, as the name implies, is a community for mobile developers. I’m excited
to see the mobile dev scene get their own community and look forward to their
first events in the spring.
</p>
<h2 class="notion-heading_2 notion-color-default">
Google Developer Student Club TUAS
</h2>
<p>
The students of Turku University of Applied Sciences have an opportunity to be
part of the
<a href="https://gdsc.community.dev/turku-university-of-applied-sciences/" class="notion-text-href">GDSC TUAS</a>
that organizes tech meetups and offers opportunities for the students to learn
new tech and network. They are currently in the early phases of their
operations with the first event organized in January.
</p>
<h2 class="notion-heading_2 notion-color-default">Turku Dev Lunch</h2>
<p>
In addition to all the more formal and organized program, we also eat lunches
together. Usually once a month in a more scheduled manner and more often than
that in ad-hoc way. We usually agree on the lunches in the
<a href="https://discord.gg/pAAARVc5F7" class="notion-text-href">TurkuDev Discord</a>
and publish some of them in
<a href="https://jarkkaa.fi/@turkudevlunch" class="notion-text-href">Turku Dev Lunch Mobilizon</a>.
</p>
Please, don’t force me to log in
2024-01-27T00:00:00Z
https://hamatti.org/posts/please-dont-force-me-to-log-in/
<p>
It feels like everything these days needs you to create an account and log in
to use them. <b class="notion-text-bold">Philips Hue</b> announced you
<a href="https://www.theverge.com/2023/9/28/23892761/philips-hue-app-account-changes" class="notion-text-href">need to plug your home’s light automation to their cloud</a>, even if you just use it locally. They claim it’s for security.
</p>
<blockquote class="notion-quote notion-color-default">
“Up until this point, the mechanism we’ve used to identify who is the owner of
the Hue system and can do this [control your bridge], is by physical access to
the device — and pressing the button on the bridge,” says Yianni. “This
approach is inadequate going forward, and we need a more robust way to
identify the owner of the system and enable them to manage their system — the
Hue account is how we will do this.”
</blockquote>
<p>
If someone breaks into my apartment, them pushing a button on my Hue bridge is
the least of my worries. Them being connected to my online account is way
higher threat.
</p>
<p>
<b class="notion-text-bold">Yle - The Finnish Public Service Media Company</b>
-
<a href="https://yle.fi/a/74-20051958" class="notion-text-href">announced that in 2024, to use their mobile app to watch the TV shows and
movies from streaming service Areena requires login (in Finnish).</a>
</p>
<p>
They do it so they can provide better algorithm recommendations. I don’t
really care about those.
</p>
<p>
All the content is free as it’s public service and they will keep the browser
use free of login (at least for now). But it adds another step of having to
log in whenever any of the devices forgets my login.
</p>
<p>
I installed <b class="notion-text-bold">Adobe’s Acrobat Reader</b> to my dad’s
phone during Christmas to help him open PDFs he received via email. It can be
used without login but it keeps pestering about the login every time,
confusing my dad who’s not sure what services need to be logged into and which
not. It’s a god damn PDF reader, no need for accounts or logins.
</p>
<p>
<b class="notion-text-bold">Arc Browser </b>requires you to create an account
so that
<a href="https://web.archive.org/web/20230926131852/https://resources.arc.net/en/articles/8129702-why-do-i-need-an-account-for-arc" class="notion-text-href">they can send you update emails</a>:
</p>
<p>
(I had to dig into an Internet Archive page since Arc deleted that answer from
their site somewhere between me writing and publishing this post. They still
require an account but don't anymore say why)
</p>
<blockquote class="notion-quote notion-color-default">
<p><b class="notion-text-bold">Why do I need an account for Arc?</b></p>
<p>
Arc currently requires an account so the team can communicate with members
about product updates, feedback, and questions. Arc also has collaborative
features, like Easel, and multi-device syncing, both of which require an Arc
identity.
</p>
</blockquote>
<p>Once again, perfectly fine for opt-ins but I don’t want one.</p>
<p>
<b class="notion-text-bold">Windows 10/11</b> have been really bad at pushing
constant “please login” screens that are annoying to users who are not sure
which pop ups on their computer are safe and not and what they mean. I don’t
own Windows PC but I help family who has those and I hate it.
</p>
<p>
I was excited to test the
<b class="notion-text-bold"><a href="https://www.warp.dev/" class="notion-text-href">Warp</a></b><a href="https://www.warp.dev/" class="notion-text-href"> terminal</a> until
I learned it requires login:
</p>
<blockquote class="notion-quote notion-color-default">
<p>
<b class="notion-text-bold">Why is login required for a terminal app? </b>
</p>
<p>
The primary reason is that login allows us to build cloud-oriented features
that make the terminal have a concept of “your stuff” and “your team’s
stuff” – for example Block Sharing.
</p>
<p>
This is the same reason other collaborative apps like Figma and Github
require login – identity is the basis of building cloud-native apps. That
said, we understand the desire to try Warp before logging in, and are
exploring product experiences that will allow users to preview Warp before
signup.
</p>
</blockquote>
<p>
It’s a terminal. All the extra features like sharing and AI can be great but
why make everything go through it rather than making it extra for those who
need those features?
</p>
<p>
These examples go on and on and on. What’s common with all of them, is that
they are all services and software that don’t
<b class="notion-text-bold">need</b> logins and accounts. They are extra
features, forced to everyone, instead of opt-in for those who want to use
these features.
</p>
<p>
I’m sick and tired of this being more and more the trend with everything. I
understand that the business of building software is tough but this is not the
solution.
</p>
<p>
On an adjacent note, social media platforms becoming “the default” causes a
lot of problems too.
</p>
<p>
Facebook, Instagram, X and others made a similar move after years of luring
everyone and their dog to their platform. A lot of companies like pubs and
restaurants went with the crowds and only had pages on these platforms instead
of building their own sites and now the customers can’t see their opening
hours or menus without signing up to these platforms owned by mega
corporations.
</p>
<p>
Recently I applied for a job that had LinkedIn link as a requirement to apply.
Not a “if you want, you can share your LinkedIn”. Not a “share link to your
CV”. A required membership to a social media platform owned by Microsoft.
</p>
<p>
These are examples of accounts required on third-party platforms. It sucks
too.
</p>
<p>
So please, stop with these hostile account requirements that make your
products worse for the users.
</p>
Python Bytes featured Syntax Error
2024-01-25T00:00:00Z
https://hamatti.org/posts/python-bytes-featured-syntax-error/
<img src="https://hamatti.org/assets/img/posts/python-bytes-featured-syntax-error/1.png" alt="Screenshot of a Youtube video player showing Michael and Brian in a video call on the left and a web browser on the right with the page for Syntax Error #11: Debugging Python open. " />
<p>
I woke up yesterday with my phone full of notifications from different people.
It turns out, the wonderful duo of Michael Kennedy and Brian Okken from
<a href="https://pythonbytes.fm/" class="notion-text-href">Python Bytes</a>
had decided to
<a href="https://pythonbytes.fm/episodes/show/368/that-episode-where-we-just-ship-open-source" class="notion-text-href">talk about</a>
my most recent
<a href="https://www.syntaxerror.tech/" class="notion-text-href">Syntax Error</a>
newsletter issue
<a href="https://www.syntaxerror.tech/syntax-error-11-debugging-python/" class="notion-text-href">Debugging Python</a>.
</p>
<p>
I’m awestruck. I absolutely love Python Bytes as it’s such a great podcast to
learn about new stuff and different tools and libraries and events in the
Python world in a bite-sized format. And to have one my own creations as one
of those things to learn about is just wonderful.
</p>
<p>
I recommend watching/listening
<a href="https://pythonbytes.fm/episodes/show/368/that-episode-where-we-just-ship-open-source" class="notion-text-href">the entire episode</a>
and subscribing to their podcast.
</p>
<p>
Big thanks to Michael and Brian for featuring my work. You truly made my day
and week.
</p>
<p></p>
Community websites with Eleventy
2024-01-24T00:00:00Z
https://hamatti.org/posts/community-websites-with-eleventy/
<p>
<strong>Update:</strong> I did a talk about this in
<a href="https://11tymeetup.dev/" target="_blank">THE Eleventy Meetup</a>
20.2.2024, you can
<a href="https://www.youtube.com/watch?v=e_H4DxqAiyY" target="_blank">watch a recording in Youtube</a>.
</p>
<p>
<a href="https://www.11ty.dev/" class="notion-text-href">Eleventy</a> is a
great and powerful static site generator especially for event-organizing
communities like meetups. Here’s my setup that I use with both
<a href="https://turkufrontend.fi/" class="notion-text-href">turkufrontend.fi</a>
and
<a href="https://archipylago.dev/" class="notion-text-href">archipylago.dev</a>.
</p>
<p>
On the root, I have <code class="notion-text-code">_data</code> folder with 5
JSON files: <code class="notion-text-code">events.json</code>,
<code class="notion-text-code">history.json</code> ,
<code class="notion-text-code">team.json</code>,
<code class="notion-text-code">sponsors.json</code>and
<code class="notion-text-code">speakers.json</code>.
</p>
<h2 class="notion-heading_2 notion-color-default">Events</h2>
<p>
For event logs, I have two files. One keeps track of current calendar (usually
one spring or fall at a time) and another one is a full history of previous
events.
</p>
<h3 class="notion-heading_3 notion-color-default">Calendar</h3>
<p>
<code class="notion-text-code">events.json</code> has an array of objects with
three keys. Dates are in YYYY-MM-DD format.
</p>
<pre class="language-json"><code class="language-json">[
{
"date": "YYYY-MM-DD",
"host": "[Sponsor name]",
"url": "[URL for registration / event info]"
}
]</code></pre>
<p>
With <code class="notion-text-code">events.njk</code> partial, these get
rendered (with Nunjucks) into a list as
</p>
<pre class="language-html"><code class="language-html"><section>
<h2>Fall 2023</h2>
<div class="event-container">
<ul id="events">
{% for event in events %}
<li>
{{ event.date | toLocalDate }} @
{% if event.url %}
<a href="{{ event.url }}">
{{ event.host }}
</a>
{% else %}
{{ event.host | formatHost }}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<div style="text-align: center">
<a href="{{ '/history' | url }}">full history</a>
</div>
</section></code></pre>
<p>
Each event lists the date and host and if URL is present, it links to the
event page.
</p>
<h3 class="notion-heading_3 notion-color-default">Full timeline</h3>
<p>
For a full history of events,
<code class="notion-text-code">history.json</code> has an array of objects
such as
</p>
<pre class="language-json"><code class="language-json">{
"date": "YYYY-MM-DD",
"host": "[Sponsor name]",
"talks": [
{
"title": "[Talk title]",
"speaker": "[Speaker name]",
"description": "[Talk abstract]",
"url": "[URL to slides/recording/etc]"
}
]
}</code></pre>
<p>
These are rendered with
<code class="notion-text-code">history.njk</code> partial as
</p>
<pre class="language-html"><code class="language-html"><div class="timeline-event">
<div class="timeline-header">
<div class="timeline-date">
{{ event.date | toLocalDateYear }}
</div>
<div class="timeline-host">
{{ event.host | formatSponsorLogoNoLink | safe }}
</div>
</div>
<div class="timeline-talks">
<ul>
{% for talk in event.talks %}
<li>
<strong>{{ talk.title }}</strong> by {{ talk.speaker }}
</li>
{% endfor %}
</ul>
</div>
</div></code></pre>
<h2 class="notion-heading_2 notion-color-default">
Speakers, Sponsors and Team
</h2>
<h3 class="notion-heading_3 notion-color-default">Speakers</h3>
<p>For speakers, I store a list of names in an array</p>
<pre class="language-json"><code class="language-json">[
"First Last",
"Firstname Lastname"
]</code></pre>
<p>
and accompanied with that, in
<code class="notion-text-code">assets/img/speakers/</code> I have portraits of
the speakers in file name format
<code class="notion-text-code">firstname-lastname.png</code>.
</p>
<p>To render them into our hall of fame, I have a partial of</p>
<pre class="language-html"><code class="language-html"><div class="gallery">
{% for name in speakers %}
{{ name | formatHallOfFame | safe }}
{% endfor %}
</div></code></pre>
<p>
where the <code class="notion-text-code">formatHallOfFame</code> filter looks
like
</p>
<pre class="language-javascript"><code class="language-javascript">formatHallOfFame: function (name) {
return `<div class="speaker">
<img src="/assets/img/speakers/${slugify(name)}.png" alt="">
<p>${name}</p>
</div>`;
}</code></pre>
<h3 class="notion-heading_3 notion-color-default">Sponsors</h3>
<p>Sponsors work similarly but has extra attribute of URL:</p>
<pre class="language-json"><code class="language-json">{
"name": "[Sponsor name]",
"url": "[URL to sponsor website]"
}</code></pre>
<p>and these are rendered with</p>
<pre class="language-html"><code class="language-html"><section>
<h2>We are working with</h2>
<div id="sponsors">
{% for sponsor in sponsors %}
{{ sponsor | formatSponsorLogo | safe }}
{% endfor %}
</div>
</section></code></pre>
<p>with the filter</p>
<pre class="language-javascript"><code class="language-javascript">formatSponsorLogo: function ({ name, url }) {
const filename = `/assets/img/sponsors/${slugify(name)}.png`;
return `<a href="${url}" target=_blank><img src="${filename}" alt="${name}"></a>`;
},</code></pre>
<h3 class="notion-heading_3 notion-color-default">Team</h3>
<p>
And finally the team is stored in
<code class="notion-text-code">team.json</code> with each organizing team
member in an array:
</p>
<pre class="language-json"><code class="language-json">[
{
"name": "[name]",
"title": "[title]",
"links": [
{
"icon": "[icon-name]",
"url": "[url]"
}
]
}
]</code></pre>
<p>
where <code class="notion-text-code">icon-name</code> maps to SVGs I have
stored. For example,
<code class="notion-text-code">"icon": "mastodon"</code> renders
<code class="notion-text-code">mastodon.svg</code> icon.
</p>
<p>This is rendered with</p>
<pre class="language-html"><code class="language-html"><section>
<h2>Team</h2>
<div id="team">
{% for person in team %}
<div class="profile">
{{ person.name | nameToImage | safe }}
<div class="profile--inner">
<p class="profile--name">
{{ person.name }}
</p>
<p class="profile--title">
{{ person.title }}
</p>
<div class="profile--links">
{% for link in person.links %}
{{ link | linkToHTML | safe }}
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</section></code></pre>
<h2 class="notion-heading_2 notion-color-default">Updating the website</h2>
<p>
Since all these are stored in data files and rendered from them, making
updates becomes a process of adding images (in case of new speakers/sponsors)
and updating JSON files.
</p>
<p>
And since all the partials are in the
<code class="notion-text-code"><a href="https://www.11ty.dev/docs/config/#directory-for-includes" class="notion-text-href">_includes/</a></code><a href="https://www.11ty.dev/docs/config/#directory-for-includes" class="notion-text-href">
folder</a>, they can be included in any part of the website, making them flexible
components which makes making changes to the layouts easier when everything is
generated.
</p>
The Lazarus Project (review)
2024-01-22T00:00:00Z
https://hamatti.org/posts/the-lazarus-project-review/
<p>
<b class="notion-text-bold">This post includes major spoilers for the first season of the 2022 TV
series </b><b class="notion-text-bold"><a href="https://www.imdb.com/title/tt14190592/" class="notion-text-href">The Lazarus Project</a></b><b class="notion-text-bold">. If you haven’t seen it yet, go watch it and then come back.</b>
</p>
<p>
First, it’s important to note that this is not the same as
<a href="https://www.imdb.com/title/tt0464041/" class="notion-text-href">the 2008 movie The Lazarus Project</a>
starring Paul Walker and has nothing to do with it, despite sharing the name.
It has also nothing to do with
<a href="https://en.wikipedia.org/wiki/The_Lazarus_Project_(novel)" class="notion-text-href">the 2008 novel The Lazarus Project</a>, despite sharing the name. Apparently, it’s just a really popular name.
</p>
<h2 class="notion-heading_2 notion-color-default">== Spoilers start here ==</h2>
<p>
The main setting in The Lazarus Project is that each July 1st, the universe is
“backed up” in a checkpoint and this group of people who run the namesake
organization have a way to reset the universe to that checkpoint. They do it
to recover from world-ending catastrophes like nuclear wars.
</p>
<p>
The main character, George gains the ability to remember these resets and
through that, gets a job at the project.
</p>
<p>
The first season is an 8-episode mini series and that was just the right
amount to binge watch in a single day.
</p>
<p>
First of all, I was really intrigued by the time travel mechanic and the
setting of the series. There’s been a few other “main character can see what’s
gonna happen so they can save the day” series that I’ve enjoyed, like
<a href="https://www.imdb.com/title/tt0115163/" class="notion-text-href">Early Edition</a>
and
<a href="https://www.imdb.com/title/tt0364817/" class="notion-text-href">Tru Calling</a>. And then there’s the “figure out what you need to change to escape the
loop” movies like
<a href="https://www.imdb.com/title/tt0107048/" class="notion-text-href">Groundhog Day</a>, the brilliant
<a href="https://www.imdb.com/title/tt11080108/" class="notion-text-href">The Map of Tiny Perfect Things</a>
which is my favorite of the genre, and
<a href="https://www.imdb.com/title/tt5640450/" class="notion-text-href">ARQ</a>.
</p>
<p>
I was expecting an epic adventure and to be honest, was a bit negatively
surprised by how the main motif turned out to be relationship drama. Normally,
I’d argue that love is a great motive for the characters to ponder how far
they are ready to go. But maybe it was my mistaken expectations that led it to
being a bit underwhelming.
</p>
<p>
The loop was bit underused in my opinion. There was a lot of flashbacks to
erased iterations of the loop which showcased how the main character ended up
together with his girlfriend in the current, saved timeline but not in the
others. Idea good, execution was bit confusing. Those same flashbacks also
seemed to hash out a relationship between Archie and Dennis but those didn’t
seem to lead anywhere either.
</p>
<p>
I also really wanted to see this secret organization taking more advantage of
the loop. Now it felt that other than a few small occasions, there wasn’t a
sense of how every iteration, the knowledge gained earlier would help these
people become better at what they do and more powerful. For me, that’s the
inevitable part of this kind of time loop - like we see in the pioneer of the
genre at Groundhog Day. While there the main character might not become a
better person, he’s picking up more and more skills which was something that
was either missing here or was heavily implied.
</p>
<p>
In one way, I liked that the show had 8 episodes and was well-contained and
easy to binge. But I felt it tried to do too much during those 8 episodes. It
teased with some world-building but I’m not convinced they will lead to
anything meaningful with the second season.
</p>
<p>
The cliffhanger in the end to set up the second season left me feeling “we’re
making a second season and it will be something different”. I haven’t seen the
second season yet so I hope I’ll be positively surprised.
</p>
<p>
Despite a bit of negative undertone of this review, I did really enjoy The
Lazarus Project’s first season and am looking forward to watching the second
season. I just wish there was either a bit more time to develop the amount of
stuff they put into the show or alternatively, a bit less stuff to focus on
with the limited runtime.
</p>
A few core memories
2024-01-20T00:00:00Z
https://hamatti.org/posts/a-few-core-memories/
<p>
This blog post is my participation in this month’s
<a href="https://indieweb.org/indieweb-carnival" class="notion-text-href">Indie Web Carnival</a>. It’s also my first time participating as I learned about the whole thing at
the end of 2023. This month’s carnival is hosted by
<a href="https://foreverliketh.is/" class="notion-text-href">https://foreverliketh.is/</a>
who chose
<a href="https://foreverliketh.is/blog/indieweb-carnival-january-2024-positive-internalization/" class="notion-text-href">positive internalization as this month’s theme</a>:
</p>
<blockquote class="notion-quote notion-color-default">
<p>
Your entry for this month’s carnival will have you intentionally seek out
positive memories.
<i class="notion-text-italic"><b class="notion-text-bold">Memories that remind you of the good parts of yourself; the facets of
your being that you want to see more of, that you wish to nurture and
grow.
</b></i>By reliving them and seeing yourself in that positive light, you help to
internalize a healthier, and more accurate, self-perception.
</p>
<p>
It’s important to note that effective internalization is about
<i class="notion-text-italic"><b class="notion-text-bold">emotions</b></i>. It’s not enough to
<i class="notion-text-italic"><b class="notion-text-bold">think</b></i> that
your memory is positive, it has to
<i class="notion-text-italic"><b class="notion-text-bold">FEEL</b></i>
positive. If you sincerely want the benefits, then this is not something you
can “logic” your way out of.
<b class="notion-text-bold">Don’t listen to your brain, </b><i class="notion-text-italic"><b class="notion-text-bold">listen to your heart.</b></i>
</p>
</blockquote>
<h2 class="notion-heading_2 notion-color-default">Not an easy task for me</h2>
<p>
When I read the introduction post the first time, I felt this was an
interesting topic. And don’t get me wrong, it absolutely is. But it’s hard. I
had to really really think and dig deep to find "<i class="notion-text-italic">Memories that remind you of the good parts of yourself</i><i class="notion-text-italic"><b class="notion-text-bold">”</b></i><i class="notion-text-italic">. </i>That maybe tells a lot about me. And even
harder was writing about those things openly.
</p>
<p>
I have no trouble writing about the negative parts and the struggles. My
mental health, difficulty in finding my place and all that. But to write about
“the good parts of myself”, yikes. To do that means I have to claim I’m good
at something or that there are good parts of me.
</p>
<p>
I have a lot of good memories but I don’t associate any of them with myself
but rather they were good memories because of other people.
</p>
<p>
Last summer, I went through a decade worth of photos, covering most of my
adult life and I did find some positive moments there.
</p>
<h2 class="notion-heading_2 notion-color-default">
A core memory #1: my first talk in PyCon CZ stage
</h2>
<p>
One of my dearest memories from the recent times is from June of 2019 when I
got an opportunity to travel to Ostrava, Czechia to speak in PyCon CZ. I had
just gotten my first Developer Advocate position in my then-current job and
this talk was my first international conference talk.
</p>
<img src="https://hamatti.org/assets/img/posts/a-few-core-memories/1.png" alt="Juhis on a stage taking a selfie with a few hundred people audience in the background. Everyone in the picture are hugging the air. " />
<p>
The talk, titled
<a href="https://hamatti.org/talks/i-teach-therefore-i-learn/" class="notion-text-href">I teach, therefore I learn</a>, went well. It was hot as heck though. The temperature in the conference
hall pictured above was around 30C and the only company swag I had with me was
a hoodie so I put it on for the talk and after 30 minutes on the stage was
sweating so much.
</p>
<p>
The presentation culminated in a Friday Hug from the stage and I love this
picture so much. There are many people in that picture who still, in 2023, are
a part of my life in a very positive way. Every time I get to visit Czechia, I
feel so warm and welcomed by all my old and future friends I meet there.
</p>
<p>
And this talk was the kick off to my new career in developer relations and it
launched my journey as a public speaker.
</p>
<h2 class="notion-heading_2 notion-color-default">
A core memory #2: hitchhiking around the country
</h2>
<p>
I’m normally a very shy, reserved and introverted guy. It takes a lot for me
to spark a discussion with a stranger.
</p>
<p>
But a wonderful thing happens when I get a backpack on my back and I’m out for
an adventure. I become a wholly different person. Around 2009-2010, I spent a
couple of summers traveling around Finland, hitchhiking my way between cities.
</p>
<p>
The summer of 2009 was the main trip. I took a train to the northern part of
Finland for an university exam and spent 3 weeks traveling back south (via the
eastern boarder and southern highways) by only travelling by hitchhiking and
staying with friends.
</p>
<p>
The longest waits were around few hours and I once encountered a bear but
luckily it didn’t see me. I was so scared though. Mind you, this was the time
before smart phones or anything like that so I didn’t have access to any maps
or social media channels during the trip.
</p>
<p>
One thing I absolutely loved about those trips was meeting new people I would
otherwise never meet and hear their stories. At best (or worst, depending on
how you look at it), I spent 12-14 hours per day to travel distances that
otherwise would have taken 3-4 hours. I saw parts of the country I had never
seen before and will never see again.
</p>
<p>
There’s something incredibly freeing about hitchhiking. It’s so random and
you’re at the complete mercy of other people’s hospitality that you can’t make
any real plans for your trip. You’ll end up somewhere and it might be where
you were heading or it might be watching NHL Stanley Cup finals in the middle
of the night in a living room of someone you met at a bar in a city you’ve
never been before because that’s where your night ended and you didn’t have a
plan forward.
</p>
<p>
I guess that’s why I loved doing it back then. Normally I’m very organized and
an over-thinker but when you’re on the road depending on the kindness of
others, you can’t be any of that. You just gotta take what’s coming and
improvise and figure stuff out.
</p>
<p>
I was extremely lucky that I never ran into any problems. The near bear
encounter was the closest one to anything negative - otherwise every encounter
with people was so nice.
</p>
<p>
I feel like during those days of me travelling, I was the kind of person I’d
like to be every day. More open, more social, more excited about stuff, more
positive. I love the Traveller Me.
</p>
<h2>Other submissions</h2>
<ul>
<li>
<a href="https://poview.org/posts/the-smell-of-firewood/">The smell of firewood | Poview</a>
</li>
<li>
<a href="https://sarajaksa.eu/2024/01/indieweb-carnival-january-2024-the-feeling-of-being-myself/">IndieWeb Carnival January 2024: The Feeling of Being Myself | Blog of
Sara Jakša</a>
</li>
<li>
<a href="https://foreverliketh.is/blog/the-pupil/">The Pupil ~ foreverliketh.is</a>
</li>
<li>
<a href="https://microbyte.neocities.org/posts/internalisation">Positive Internalization :: Microbyte</a>
</li>
<li>
<a href="https://www.jeremycherfas.net/blog/very-positive-memories">Very Positive Memories | Jeremy Cherfas</a>
</li>
<li>
<a href="https://manuelmoreale.com/positive-internalization">Positive Internalization – Manu</a>
</li>
<li>
<a href="https://tangiblelife.net/postive-internalization">Positive Internalization - Tangible Life</a>
</li>
<li>
<a href="https://whitevhs.xyz/articles/2024/01/29/the-little-things">Reflection and Positive Internalization</a>
</li>
<li>
<a href="https://psychcool.neocities.org/BlogPosts/Psychology/nostalgia">Nostalgia</a>
</li>
<li><a href="https://www.zinzy.website/2024/01/31/flutter/">Flutter</a></li>
</ul>
I wish Python had Integer.times
2024-01-17T00:00:00Z
https://hamatti.org/posts/i-wish-python-had-integer-times/
<p>
An old adage says that we read the code more than we write it. To make code
more readable, we pay attention to variable and function naming and the
available structures in the language. Sometimes we use the language long
enough that we become used to certain mechanisms and how they work.
</p>
<p>
I was thinking about this last month when I was solving
<a href="https://adventofcode.com/" class="notion-text-href">Advent of Code puzzles</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">
Looping <code class="notion-text-code">n</code> times
</h2>
<p>
In Ruby, integers have a method
<a href="https://ruby-doc.org/core-2.5.1/Integer.html#method-i-times" class="notion-text-href">Integer#times</a>
that returns an Enumerator with values from 0 to n-1. This means we can do:
</p>
<pre class="language-ruby"><code class="language-ruby">5.times { | _ | puts "This is printed five times" }
# or even
5.times { puts "This is printed five times" }</code></pre>
<p>
In Python, to achieve functionally similar result, we use
<code class="notion-text-code">range</code>:
</p>
<pre class="language-python"><code class="language-python">for _ in range(5):
print('This is printed five times')</code></pre>
<p>
When I write Python though, I often would like to be clearer that I’m
repeating something N times. Both
<code class="notion-text-code">Integer#times</code> in Ruby and
<code class="notion-text-code">range</code> in Python iterate over a range
from 0 to n-1 but they read very differently.
</p>
<p>
In Python, one could also use
<a href="https://docs.python.org/3/library/itertools.html#itertools.repeat" class="notion-text-href">itertools.repeat</a>
which takes two arguments: object to return and how many times it is repeated:
</p>
<pre class="language-python"><code class="language-python">from itertools import repeat
for _ in repeat(None, 5):
print('This is printed five times')</code></pre>
<p>
I do have a suspicion though that often this would get flagged in code review
and asked to switch to <code class="notion-text-code">range</code> which saves
one import. And I guess most intermediate Python developers know how
<code class="notion-text-code">range</code> work, making it understandable
enough.
</p>
<h2 class="notion-heading_2 notion-color-default">
A solution? Alias <code class="notion-text-code">range</code> with
<code class="notion-text-code">times</code>
</h2>
<p>
Every time I write <code class="notion-text-code">for _ in range(n)</code>, I
wish there was an alias to use
<code class="notion-text-code">for _ in times(n)</code>. I think it would make
it clearer when reading that here we don’t care about values, just how many
times the loop should be run.
</p>
<p>
I feel like this aliasing would be more pythonic than adding
<code class="notion-text-code">times</code> method to
<code class="notion-text-code">int</code> class. But it could also be that:
</p>
<pre class="language-python"><code class="language-python">for _ in (5).times():
print('This is printed five times')</code></pre>
<p>
The difference between <code class="notion-text-code">range</code> and
<code class="notion-text-code">times</code> is not huge but I feel it’s
important. It conveys the message of what our code is doing. When we want to
iterate over a range, we use <code class="notion-text-code">range</code> and
when we want to just repeat something, we use
<code class="notion-text-code">times</code>.
</p>
Syntax Error #11: Debugging Python
2024-01-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-11-debugging-python/
<p>
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this January issue of Syntax Error I
shared my recent conference and meetup talk Debugging Python in written form.
Read full article at
<a href="https://www.syntaxerror.tech/syntax-error-11-debugging-python/">syntaxerror.tech/syntax-error-11-debugging-python/</a>
and either subscribe to the email or RSS feed to catch all of them.
</p>
Debugging Python
2024-01-13T00:00:00Z
https://hamatti.org/posts/debugging-python/
<p>
<i class="notion-text-italic">This blog post is a written form of my presentation Debugging Python
(recently given in </i><i class="notion-text-italic"><a href="https://pycon.se/" class="notion-text-href">PyCon Sweden ‘23</a></i><i class="notion-text-italic"> and </i><i class="notion-text-italic"><a href="https://archipylago.dev/" class="notion-text-href">archipylago #1</a></i><i class="notion-text-italic">).</i>
</p>
<h2 class="notion-heading_2 notion-color-default">What is debugging?</h2>
<p>
In essence, debugging is the process of figuring out what’s wrong when the
<i class="notion-text-italic">computer says no</i>. That could manifest itself
as an error (software crashing) or logical error (software doing something in
a way it’s not meant to do). The ones that come with an error are usually a
bit easier to start with because there’s a starting point for you to do your
detective job. That doesn’t mean they are easy to solve though.
</p>
<p>I really like this quote:</p>
<blockquote class="notion-quote notion-color-default">
“Debugging starts with your current body of knowledge and ends by answering
the question: What is happening?” -
<a href="https://zinascii.com/2023/debugging-a-zig-test-failure.html" class="notion-text-href">Ryan Zezeski</a>
</blockquote>
<p>
We all start somewhere: we have some context in our mind about the current
codebase, feature, and - if it’s a recently developed feature - issue at hand.
We then apply different actions, using tools and techniques, to discover more
and pin point what is actually happening.
</p>
<p>
This blog post (and my usual definition of debugging) does not discuss solving
those problems (by writing code) or mitigating them (by writing tests,
documentation, code reviews and so on). I focus on things that help developers
become better and more efficient in finding what’s wrong.
</p>
<p>
There are two main parts to my approach:
<b class="notion-text-bold">the debugging mindset</b> which includes
non-technical things and
<b class="notion-text-bold">tools & techniques </b>which includes tooling used
on the computer to debug. You need to master both to become efficient. If
you’re great with the mindset but don’t know the tools, you’ll likely reach
the goal but it will be slow and very manual. If on the other hand, you know
the tools well but don’t have a good mindset, you might end up doing
unnecessary things.
</p>
<h2 class="notion-heading_2 notion-color-default">Debugging mindset</h2>
<p>
What do you do when you notice something is wrong? Depending on the situation
and timing, it may cause a panic response. It’s Friday afternoon and you
notice something’s broken in production and the pressure might set in.
</p>
<h3>Take a break</h3>
<p>
In my opinion, the most important first step is to
<b class="notion-text-bold">stop,</b>
<b class="notion-text-bold">take a deep breath</b> and assess the situation.
Rushing only leads to more issues in the long run. You should make sure you
understand what happened and follow the steps of debugging.
</p>
<p>
One thing I see way too often experienced developers do (and I’ve been guilty
of it myself too) is make guesses. We have a tendency to think we know what’s
happening and why and jump right into the code. Then we try to read the code
to identify the problem. It doesn’t work because software is complex and
mistakes often really difficult to spot. Especially when we think what the
outcome should be and it makes us not see the problems.
</p>
<h3>Step-by-step process</h3>
<p>
Instead of making guesses and assumptions, it’s best to adopt
<b class="notion-text-bold">a process</b>. A step-by-step approach where you
double check everything and don’t let yourself jump to conclusions. The
process is iterative and you repeat it every time making small progress until
you finally reach either a dead end or the solution.
</p>
<img src="https://hamatti.org/assets/img/posts/debugging-python/1.png" alt="It's the Gru Makes A Plan meme format. His plan: Need to debug an issue. How do we do this? Set a breakpoint. But the breakpoint never hits. This breakpoint never hits? He looks back at the board in confusion " />
<p>
The first, crucial step is to make sure the code you think is running, is
actually running.
<a href="https://twitter.com/JenMsft/status/1256007715425382400/" class="notion-text-href">The above meme from @JenMsft’s tweet</a>
is so spot on. I can’t emphasise the importance of this enough. There are many
reasons this could happen and if you let the tunnel vision take over, it’s
easy to miss them.
</p>
<p>
First, it might be that the code you’re modifying and the software you’re
running
<a href="https://www.syntaxerror.tech/syntax-error-4-refreshing-wrong-window/" class="notion-text-href">are not the same</a>. Maybe you’re accidentally running the production or staging site of your
web application instead of your development. Been there, done that. Or maybe
an older dev server is running on different code and overriding your later dev
server. Or the automatic build tool isn’t picking up the changes and updating
your software.
</p>
<p>
In all of these cases, I first make a small change, usually a visual one, to
make sure it gets picked up. Add a <code class="notion-text-code">1</code> to
a heading and reload your software. If things aren’t changing, you’ll avoid
wasting a lot of time figuring out why your fixes aren’t working.
</p>
<p>
Second, it might be that your assumption of the flow inside the codebase isn’t
correct. Maybe an API end point you think should be called isn’t or a
different function is responsible for it. Printing something like
<code class="notion-text-code">print("::DEBUG:: Code was run")</code> helps
you confirm your assumptions quickly. We’ll talk more about printing in just a
bit.
</p>
<h3>Talk to them ducks</h3>
<p>
<a href="https://www.syntaxerror.tech/syntax-error-3-who-let-the-ducks-out/" class="notion-text-href">Rubber duck debugging</a>
is a wonderful technique for solving problems. You may have heard about it
before. It’s partly an inside joke in the industry but it also works.
</p>
<p>
In rubber duck debugging, you pick your favorite duck and ask it to help you.
You explain your situation, what you’ve tried so far and what you think might
be the issue - the same way you would ask a colleague. Often, before the duck
has time to reply, you’ve figured the issue out yourself.
</p>
<p>
One reason why this works so well, is that we tend to be very good at skipping
details when we think about them. We convince our own brain that we did
something or definitely tried that other thing, omitting crucial details. When
we talk about them to someone else, we tend to not skip so much because we
know they are important and then we notice what we actually missed earlier.
</p>
<p>
If there’s a shortage of ducks at your location, I’ve found
<b class="notion-text-bold">brain dump </b>to be a alternative solution. I
take a page or two from my notebook and write down the things I would have
told the ducks. What’s the problem, what have I tried, what I think the
problem is. I then <b class="notion-text-bold">go for a walk</b> and think
about something completely different or
<b class="notion-text-bold">take a nap</b>. The subconscious keeps working
once we stop the conscious solving part.
</p>
<h2 class="notion-heading_2 notion-color-default">Tools & Techniques</h2>
<p>Let’s then take a look at the Python specific tools and techniques.</p>
<h3>Printing is the best debugging tool</h3>
<p>
Printing to the console is the thing we usually learn first when we start
learning programming or pick up a new language. And it has a lot of uses in
building software.
</p>
<p>
I argue
<a href="https://www.syntaxerror.tech/syntax-error-2-print-it-like-a-boss/" class="notion-text-href">printing is the best debugging tool</a>. It seems to be controversial and every time I talk about it, I get a lot of
questions and comments from people who say their teammates or mentors or more
senior developers tell them to not use print and make them feel bad for doing
so. Some people feel like printing is not “advanced enough” to be used if
you’re an experienced developer.
</p>
<p>
The reason printing is the best tool – despite not being the most powerful or
more advanced – is because it has
<b class="notion-text-bold">the lowest friction</b>. Everyone knows how to do
it, you don’t need to install any new libraries and you don’t need to
configure anything. You just throw in a print statement and run your software
and then read the output. It takes only a few seconds to add it in and you can
do it with pretty much any codebase, no matter how familiar you are with it.
</p>
<p>
With Python, we use <code class="notion-text-code">print()</code> function to
print things:
</p>
<pre class="language-python"><code class="language-python">print('::DEBUG:: This code was run')</code></pre>
<p>
Since Python 3.6, we’ve had access to
<a href="https://peps.python.org/pep-0498/" class="notion-text-href">f-strings</a>
(with a helpful
<a href="http://fstring.help/" class="notion-text-href">fstring.help</a>
cheatsheet page) that make it easier to add variables and expressions inside
prints:
</p>
<pre class="language-python"><code class="language-python">user = 'Juhis'
print(f'{user} logged in')
# Prints "Juhis logged in"</code></pre>
<p>And if you’re doing something like this:</p>
<pre class="language-python"><code class="language-python">print(f'user = {user}, email = {email}')</code></pre>
<p>you can shortcut it with</p>
<pre class="language-python"><code class="language-python">print(f'{user=}, {email=}')</code></pre>
<p>
Printing is a good way to quickly examine what different variables in our code
store.
</p>
<p>
But it can also become a bit cumbersome and slow when you need to do a lot of
exploration as it requires you to run the software again every time you make
changes.
</p>
<h3>Snoop is a toolkit for more</h3>
<h4></h4>
<p>
The next step is to use tools from
<a href="https://github.com/alexmojaki/snoop" class="notion-text-href">snoop library</a>. The basic usage of <code class="notion-text-code">snoop</code> is to
decorate a function with it and get a line-by-line output of the execution
with all the state changes printed out. From their example in readme:
</p>
<pre class="language-python"><code class="language-python">import snoop
@snoop
def number_to_bits(number):
if number:
bits = []
while number:
number, remainder = divmod(number, 2)
bits.insert(0, remainder)
return bits
else:
return [0]
number_to_bits(6)</code></pre>
<p>when ran, will print</p>
<pre class="language-python"><code class="language-python">15:42:39.18 >>> Call to number_to_bits in File "/example.py", line 4
15:42:39.18 ...... number = 6
15:42:39.18 4 | def number_to_bits(number):
15:42:39.18 5 | if number:
15:42:39.18 6 | bits = []
15:42:39.18 7 | while number:
15:42:39.18 8 | number, remainder = divmod(number, 2)
15:42:39.18 .................. number = 3
15:42:39.18 .................. remainder = 0
15:42:39.18 9 | bits.insert(0, remainder)
15:42:39.18 .................. bits = [0]
15:42:39.18 .................. len(bits) = 1
15:42:39.18 7 | while number:
15:42:39.18 8 | number, remainder = divmod(number, 2)
15:42:39.18 .................. number = 1
15:42:39.18 .................. remainder = 1
15:42:39.18 9 | bits.insert(0, remainder)
15:42:39.18 .................. bits = [1, 0]
15:42:39.18 .................. len(bits) = 2
15:42:39.18 7 | while number:
...</code></pre>
<p>
You can see from the example output how it prints each iteration of the while
after each other, showing how the values change. This is handy when you have
multiple variables that keep changing as you only need to run it once and
you’ll get all the changes for all the variables in scope.
</p>
<h4></h4>
<p>
<code class="notion-text-code">snoop.pp</code> is a function that can be used
to print values in the middle of expressions. It prints the argument passed in
and also returns it so it can be put in anywhere:
</p>
<pre class="language-python"><code class="language-python">from snoop import pp
x = 1
y = 2
pp(pp(x + 1) + max(*pp(y + 2, y + 3)))</code></pre>
<p>prints</p>
<pre class="language-python"><code class="language-python">12:34:56.78 LOG:
12:34:56.78 .... x + 1 = 2
12:34:56.78 LOG:
12:34:56.78 .... y + 2 = 4
12:34:56.78 .... y + 3 = 5
12:34:56.78 LOG:
12:34:56.78 .... pp(x + 1) + max(*pp(y + 2, y + 3)) = 7</code></pre>
<p>
I find its biggest weakness to be that cleaning them up after you’re done with
your debugging session becomes a bit much work, especially if used a lot
inside complex expressions.
</p>
<p>
This works similar to another library called
<a href="https://github.com/gruns/icecream" class="notion-text-href">icecream</a>
which it is inspired by.
</p>
<h4></h4>
<p>
Finally, snoop offers a way to combine snoop with a great debugger called
<a href="https://github.com/alexmojaki/birdseye" class="notion-text-href">birdseye</a>. I’ll talk more about this at the end of the debugger section below.
</p>
<h3>Debuggers</h3>
<p>
The third “level” of debugging tools are debuggers. On the base level, what a
debugger does is that it suspends the execution of code at a given point and
injects you into it. It then gives you granular control to move around in the
code, executing lines and running arbitrary Python code to examine it further.
</p>
<p>
Compared to printing or snooping, the power of debuggers is that you don’t
have to keep making changes and re-running the code every time you want to see
something different. You’ll have powerful access to the code with Python’s
great REPL interface to try things out and follow the code step by step to
find out the issue.
</p>
<p>
Python community has built many debuggers so everyone can find one that fits
their flow the best. I’ll introduce few of my favorites here but there are
many many more.
</p>
<h4>Controlling the debugger with</h4>
<p>
You can choose which debugger to use by defining it in
<a href="https://docs.python.org/3/using/cmdline.html?highlight=pythonbreakpoint#envvar-PYTHONBREAKPOINT" class="notion-text-href">PYTHONBREAKPOINT</a>
environment value.
</p>
<pre class="language-bash"><code class="language-bash"># Run IPython Debugger (replace `ipdb` with any other debugger)
PYTHONBREAKPOINT=ipdb.set_trace
# Don’t stop on breakpoint()s
PYTHONBREAKPOINT=0
# Run default Python Debugger
PYTHONBREAKPOINT=</code></pre>
<p></p>
<p>
To choose a non-default debugger, you can set
<code class="notion-text-code">PYTHONBREAKPOINT</code> to the
<code class="notion-text-code">set_trace</code> function of your favorite
debugger (you need to install them!).
</p>
<p>
By setting the value to 0, you can skip all breakpoints which can be handy to
set on a production environment or CI to make sure things don’t stop if a
breakpoint accidentally makes its way to production code.
</p>
<p>
Finally, by setting it to empty string (or not setting it at all) will use the
default Python Debugger.
</p>
<h4>Invoking the debugger</h4>
<p>
To kick off a debugger, you need to add
<code class="notion-text-code">breakpoint()</code> to your code in a line
where you want to suspend the execution:
</p>
<pre class="language-python"><code class="language-python">def find_collection(collection_name):
url = f"{BASE_URL}/Collections"
req = requests.get(url)
content = req.content
soup = BeautifulSoup(content, “html.parser")
breakpoint()
collection_tds = soup.css.select(f"#{collection_name}table td")</code></pre>
<p>
When the code execution hits this function and then the line 6, it will start
the debugger and you’ll be able to examine the state of the program and move
around.
</p>
<h4></h4>
<p>
The default Python debugger is called
<code class="notion-text-code"><a href="https://docs.python.org/3/library/pdb.html" class="notion-text-href">pdb</a></code><a href="https://docs.python.org/3/library/pdb.html" class="notion-text-href">
or The Python Debugger</a>. It looks very similar to the Python REPL but adds a bunch of helpful
commands for operating the debugger. A couple of the most helpful ones are:
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">(n)ext</code> (the letter in parentheses is a
shortcut for the entire command), you can execute current line and move to
the next. If there’s a function call on that line, it gets executed
completely.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">(s)tep</code> does the same as next but it
enters functions when called.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">(w)here</code> prints the current stack
trace, letting you know exactly where in the execution you are.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">(u)p</code> and
<code class="notion-text-code">(d)own</code> move you up and down in the
stack.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">(c)ontinue</code>, you can run the code until
the next breakpoint (or end of script).
</li>
</ul>
<h4>ipdb - IPython Debugger</h4>
<p>
If you’re used to running IPython as your REPL, you’ll be right at home with
the
<a href="https://github.com/gotcha/ipdb" class="notion-text-href">IPython Debugger</a>. It combines the functionality of the pdb with IPython REPL’s functionality.
</p>
<h4>PuDB debugger</h4>
<p>
<a href="https://pypi.org/project/pudb/" class="notion-text-href">PuDB</a> is
a terminal UI debugger, starting a multi-pane debugging session:
</p>
<img src="https://hamatti.org/assets/img/posts/debugging-python/2.png" alt="PuDB debugger open in the terminal with five panes: code, variables, stack, breakpoints and command line. A breakpoint is active. " />
<p>
Compared to <code class="notion-text-code">pdb</code> and
<code class="notion-text-code">ipdb</code>, a big benefit is seeing many
things at once. You can see the code currently being run, the current local
variables, stack and breakpoints. And there’s still space for the REPL.
</p>
<p>
Terminal UIs can take a bit to get used to if you haven’t used them a lot.
</p>
<p>
If you’re more used to web interfaces, check out the next debugger,
<code class="notion-text-code">web-pdb</code>
</p>
<h4>web-pdb debugger</h4>
<p>
<a href="https://pypi.org/project/web-pdb/" class="notion-text-href">web-pdb</a>
starts a local web server when invoked with
<code class="notion-text-code">breakpoint()</code> and offers a similar
multipane view like PuDB but on your browser:
</p>
<img src="https://hamatti.org/assets/img/posts/debugging-python/3.png" alt="web-pdb debugger active on a browser with four panes: code, globals, locals and pdb console " />
<p>
It gives you similar panes with code and variables and the pdb REPL but also
buttons that can be clicked with a mouse. I find it a good alternative to PuDB
if you don’t like navigating in terminal.
</p>
<h4>birdseye</h4>
<p>
For me, the most impressive of the debuggers is the
<a href="https://pypi.org/project/birdseye/" class="notion-text-href">birdseye</a>
debugger I mentioned earlier. To debug a function, you decorate it with
<code class="notion-text-code">birdseye.eye</code> (or with
<code class="notion-text-code">snoop.spy</code>):
</p>
<pre class="language-python"><code class="language-python">from birdseye import eye
@eye
def find_collection(collection_name):
...</code></pre>
<p>
You then call your function by running the code and it records the execution.
To view it, you can start the birdseye web server with
<code class="notion-text-code">python -m birdseye</code> and navigate to it
with your browser.
</p>
<p>
What makes birdseye so good is that it gives you very granular view into
what’s happening. Not only can you view the value after a line has finished
running but you can see the outcome of individual expressions within any line:
</p>
<img src="https://hamatti.org/assets/img/posts/debugging-python/4.png" alt="Birdseye report with find_collection function having run once with each expression in the code surrounded by a rectangle. " />
<p>
Every rectangle in the image above is a expression you can examine by clicking
it. You can click multiple ones to keep watching them.
</p>
<p>
And for loops, you can adjust the iteration counter on the left to see what
the values were on each iteration separately. I love it.
</p>
<img src="https://hamatti.org/assets/img/posts/debugging-python/5.png" alt="A closeup on birdseye debugger with two expressions, name and href selected and their values visible at the bottom of the screen. Loop iteration is 2. " />
<p>
Birdseye balances nicely the amount of information it records (which is a lot)
with how much you see (which you control).
</p>
<p>
It has a slightly different approach than other debuggers so it may not be the
best solution for every situation and I often find myself combining it with
other debuggers (and printing) depending on the type and difficulty of bugs.
</p>
<h3>Quick-fire Django tooling round</h3>
<p>
A couple tools used for debugging Django apps specifically, without going into
them in-depth.
</p>
<h4>Debugger in templates</h4>
<p>
To trigger a debugger from Django templates, add this to your custom filters:
</p>
<pre class="language-python"><code class="language-python">@register.filter
def pdb(element):
breakpoint()
return element
## And use it in templates
{{ msg | pdb }}</code></pre>
<h4>Django Debug Toolbar</h4>
<p>
<a href="https://django-debug-toolbar.readthedocs.io/" class="notion-text-href">Django Debug Toolbar</a>
is a library that adds a debugger sidebar to the frontend. It lets you examine
requests made, SQL queries ran and other handy information.
</p>
<h4>Kolo</h4>
<p>
<a href="https://kolo.app/" class="notion-text-href">Kolo</a> is a new VS Code
extension that offers much of the same information than Django Debug Toolbar
but inside your VS Code. If you’re using that to edit your code, it’s handy to
have the information directly in your code editor. Kolo is still currently in
beta but I think it’s worth checking out.
</p>
<p>
As it’s a development-only tooling, I see way less issues in already picking
it up as part of your workflow compared to something that would get shipped as
part of the production app.
</p>
<h2 class="notion-heading_2 notion-color-default">
Learn more from Syntax Error
</h2>
<p>
<a href="https://www.syntaxerror.tech/" class="notion-text-href">Syntax Error</a>
is a debugging newsletter I write to help developers turn stressful debugging
situations into joyful explorations. It’s not Python specific but instead I
try to write general tips for all developers and explore how different
technologies and languages approach debugging so we can all learn from each
other’s work.
</p>
<p>
You can subscribe to it via email or RSS or just read it on the website.
Whatever you find easiest and most comfortable.
</p>
Join us at Future Frontend 2024
2024-01-10T00:00:00Z
https://hamatti.org/posts/join-us-at-future-frontend-2024/
<p>
<b class="notion-text-bold">Future Frontend 2024 will be organized in Paasitorni, Helsinki June
13-14!</b>
</p>
<p>
Last year, we pivoted from React Finland conferences to a slightly different
topic and ran a successful first Future Frontend conference. This year, we’re
coming back with the second edition and I’m very excited for what’s to come.
</p>
<p>
I wrote
<a href="https://hamatti.org/e1b834ce469e4903b6bf15bb6f862292" class="notion-text-href">about last year’s event in my blog last summer</a>
if you want to see what that one was like.
</p>
<p>
All photos are from
<a href="https://www.flickr.com/photos/react-finland/albums/72177720309004753/with/52973571612" class="notion-text-href">Future Frontend 2023</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">
Summer days that never end
</h2>
<img src="https://hamatti.org/assets/img/posts/join-us-at-future-frontend-2024/1.png" alt="A row of tables on a terrace of a pub, full of people having drinks. " />
<p>
If you’ve never been to Finland during summer, you’re missing out. It’s a
beautiful time to be around as the sun barely sets at all during the night.
You can spend the day time in the conference learning what’s the newest trends
in frontend world and the evenings with conference buddies (or by yourself)
exploring the beautiful Helsinki.
</p>
<h2 class="notion-heading_2 notion-color-default">Conference program</h2>
<p>
To quote
<a href="https://futurefrontend.com/blog/ff24/" class="notion-text-href">our official blog post</a>, here’s this year’s conference in a nutshell:
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
Held 13.-14.6 at Paasitorni, Helsinki, Finland
</li>
<li class="notion-bulleted_list_item notion-color-default">
Workshops take place 11.-12.6
</li>
<li class="notion-bulleted_list_item notion-color-default">
Conference days are organized into eight themed sessions with two speakers
per each within a single track
</li>
<li class="notion-bulleted_list_item notion-color-default">
Our themes include compilers, artificial intelligence, design,
ui/design/edge, green computing, standardization, user interaction, and
demonstrations
</li>
<li class="notion-bulleted_list_item notion-color-default">
Each session takes 1.5h and it is up to the speaker pair to make most out of
it. The benefit of the arrangement is that often the viewpoints can
complement each other while giving good ways for audience to interact
through QAs and improvized panels.
</li>
<li class="notion-bulleted_list_item notion-color-default">
The single track format gives uniform experience to all and we have taken
care to include ample breaks so you have time to network and reflect
</li>
<li class="notion-bulleted_list_item notion-color-default">
To end the week, we organize and afterparty (included to the conference
ticket price). Usually local companies run their own meetups and happenings
during the conference week on top of this.
</li>
</ul>
<p>
Our international
<a href="https://futurefrontend.com/speakers/" class="notion-text-href">speaker line-up</a>
is fantastic one too. Last year, my favorite talk was
<a href="https://www.youtube.com/watch?v=f8XpeszsRLI" class="notion-text-href">Stephanie Nemeth’s Hacking meaningful connections with humans by talking to
(toy) rodents</a>
and she’s coming back this year too.
<a href="https://github.com/codehag" class="notion-text-href">Yulia Startsev</a>
is a fantastic developer and speaker who I’m excited to see in the conference
this year. I don’t know if
<a href="https://wtw.dev/" class="notion-text-href">Ben Holmes</a> will bring
his whiteboard on the stage but regardless, I can’t wait to see what kind of
inspirational stuff he brings to the event. For those into design systems, we
have a treat as
<a href="https://twitter.com/th4is_ds" class="notion-text-href">Thaís Santos</a>
returns to Future Frontend stage this summer.
</p>
<img src="https://hamatti.org/assets/img/posts/join-us-at-future-frontend-2024/2.png" alt="Tero, Stephanie and Jani sitting behind a wooden desk that has a clock in the front face. Stephanie is holding a microphone while Tero and Jani are wearing headset mics. " />
<p>
The two main conference days are split into topical sessions with a couple of
talks and a shared Q&A session with the speakers.
</p>
<p>
What I really like about the event though is our community. We have lovely
people join these conferences from around the world and I invite you to be
part of that community.
</p>
<p>
<a href="https://ti.to/future-frontend/2024" class="notion-text-href">The tickets</a>
to the conference is 499 eur with workshops ranging from 99 eur to 299 eur
each.
</p>
<p>See you in Helsinki next June!</p>
<p></p>
Write it anyway
2024-01-08T00:00:00Z
https://hamatti.org/posts/write-it-anyway/
<p>
I was reading James’ blog and on one of his recent posts,
<a href="https://jamesg.blog/2024/01/06/a-blog-post-behind-the-scenes/" class="notion-text-href">How a blog post came to be: Behind the scenes</a>, he wrote about the uncertainty of posting when others have already written
about the same topic:
</p>
<blockquote class="notion-quote notion-color-default">
I have wanted to write about this topic for a few months, but one thing
stopped me: my wonder about whether the topic had already been covered enough,
for other bloggers have written calls to action to start a blog. With that
said, the more such content is available -- and the more we explore ways to
articulate the importance of personal websites! -- the more we can get the
word out. My core takeaway: don't let the fact someone else has written about
a topic stop you from writing about it, too. Your perspectives matter.
</blockquote>
<p>
When I started blogging a decade ago, this was one of my main reasons to not
write about something. Whatever (technical) topic that came to my mind as a
possible blog post, I would think: “Others have already written about this and
much better than I would” and not write at all.
</p>
<p>
I did get over that thinking a few years ago but only recently I’ve really
found a satisfying reason for myself to do it. And it’s in the similar vein as
James’ takeaway.
</p>
<p>
Even if someone has written about the same topic before, having more blog
posts about that topic written
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
Increases the odds that someone finds a blog post about the topic
</li>
<li class="notion-bulleted_list_item notion-color-default">
Introduces more perspectives, voices and ways of explaining topics
</li>
</ul>
<p>
Not every post in the Internet need to be completely unique about a topic
nobody else has ever written about. So share your insights and experiences and
views on the topic, link to others that expand or contradict your views and
make it easier for people to find out about new things as they run into your
blog post.
</p>
<h2>Additional reading</h2>
<ul>
<li>
<a href="https://artemis.sh/2024/01/17/duplicate-information.html">If you can't find it, it needs to be written by Artemis Everfree</a>
</li>
</ul>
Workaround for Notion’s lack of heading levels
2024-01-06T00:00:00Z
https://hamatti.org/posts/workaround-for-notions-lack-of-heading-levels/
<h2 class="notion-heading_2 notion-color-default">The issue with headings</h2>
<p>
I
<a href="https://hamatti.org/posts/website-rewrite-and-switching-to-notion-as-cms/" class="notion-text-href">use Notion as my headless CMS for these blog posts</a>. I recently ran into an issue as I need to add fourth heading level (<code class="notion-text-code"><h4></code>) to my blog post but Notion doesn’t render a heading if you try to make a
level 4 heading. So I had to come up with a workaround.
</p>
<p>
As I googled this issue, I ran into
<a href="https://www.reddit.com/r/Notion/comments/fd91cw/comment/jgfrzwi/" class="notion-text-href">this Reddit comment</a>:
</p>
<blockquote class="notion-quote notion-color-default">
<p>
For the sake of having further indented headers, I just use H3’s, however
for H4 and beyond I put an “→” before the H3 title.
</p>
<p>H4 i put: “→ [header text]”</p>
<p>H5 i put: “→ → [header text]”</p>
<p>H6 i put “→ → → [header text]”</p>
<p>
This way I can have those Table of Contents linked at the top of the page
with slight indentation. If you don’t like that arrow I use then use an
emoji or other character-shape... it’s mostly about the concept 🤷🏻♂️
</p>
</blockquote>
<p>
While this doesn’t make it semantically proper in Notion, I realized it’s not
an issue in my case because the Notion notes are only visible to me and what
gets rendered into the blog is a case of its own.
</p>
<h2 class="notion-heading_2 notion-color-default">
The workaround with notion-render
</h2>
<p>
I use
<a href="https://github.com/kerwanp/notion-render" class="notion-text-href">notion-render</a>
library to render HTML from Notion’s blocks and what is great about that
library is that you can define your own block renderers. It’s a life saver
really, I would have already moved away from Notion if it wasn’t for this
library.
</p>
<p>Here’s my custom renderer for Notion’s Heading 3 elements:</p>
<pre class="language-javascript"><code class="language-javascript">const headingRenderer = createBlockRenderer(
"heading_3",
async (data, renderer) => {
const content = data.heading_3.rich_text[0].text.content;
if (content.startsWith("→ ")) {
/** Since Notion doesn't support heading levels beyond h3,
* I use this trick to append -> in front of a H3 that I want to be a <h4>
*/
heading = content.replace("→ ", "");
return `<h4>${heading}</h4>`;
} else {
return `<h3>${content}</h3>`;
}
}
);</code></pre>
<p>
What it does is it checks if the heading’s content starts with → and if it
does, it renders it into a <code class="notion-text-code">h4</code> element
instead (and removes the arrow). And if I ever need
<code class="notion-text-code"><h5></code> or
<code class="notion-text-code"><h6></code>, I can add extra cases for
that in my renderer.
</p>
<h2 class="notion-heading_2 notion-color-default">
I’m tempted to build my own CMS
</h2>
<p>
Ever since I started writing code as a teenager, there’s been this myth or
legend about every (web) developer wanting to build their own CMS and most of
the devs failing at it. So I’ve always tried to steer away from that.
</p>
<p>
But I ran out of features in Ghost and now it’s been about 4 months with
Notion and I already have hacks in place with alt texts for images and now
heading levels. And I hate long-term hacks. They are great workarounds for
short term but shouldn’t be required if using proper tooling.
</p>
<p>
So I’m considering building a custom headless CMS with Django that fits my
specific purpose and allows all sorts of cool things.
</p>
<p></p>
Advent of Code 2023 retrospective
2024-01-03T00:00:00Z
https://hamatti.org/posts/advent-of-code-2023-retrospective/
<p>
Last month was spent once again helping elves figure out this Christmas thing.
I’m of course talking about
<a href="https://adventofcode.com/" class="notion-text-href">Advent of Code</a>, developers’ puzzle advent calendar by the wonderful
<a href="http://was.tl/" class="notion-text-href">Eric Wastl</a>. As I’ve done
the past few years, I solved the puzzles with Python using Jupyter Notebook
and wrote
<a href="https://hamatti.org/adventofcode/2023/" class="notion-text-href">explanations of my solutions</a>
with Python education sparkled in.
</p>
<p>
<b class="notion-text-bold">This blog post contains spoilers for some of the puzzles so if you don’t
want those, head out to </b><b class="notion-text-bold"><a href="https://hamatti.org/4e1d1000e5e540588d2bd86e8da6f612" class="notion-text-href">/blog</a></b><b class="notion-text-bold"> and find another blog post to read.</b>
</p>
<p>
By the 25th day, I reached 36 stars. I did not even take a look at the puzzles
for days 20-25 though as I was spending those days preparing for and
celebrating Christmas with the family. I might revisit them though on a better
time.
</p>
<p>
This post is a collection of thoughts that crossed my mind during these 25
days.
</p>
<h2 class="notion-heading_2 notion-color-default">
Philosophical ponderings: what makes a number?
</h2>
<p>
The first day kicked off the month with quite a puzzle. I can’t remember when
early day puzzles have generated as much discussion as this one - especially
the kind of more philosophical discussion. In the puzzle’s first part, we were
tasked with finding the first and last digit in each line. For example, a line
<code class="notion-text-code">ghd4kjsdfksd78</code> would have resulted in
the first one being 4 and last 8.
</p>
<p>In the second part, there was a twist (as usual). This time,</p>
<blockquote class="notion-quote notion-color-default">
It looks like some of the digits are actually
<i class="notion-text-italic">spelled out with letters</i>:
<code class="notion-text-code">one</code>,
<code class="notion-text-code">two</code>,
<code class="notion-text-code">three</code>,
<code class="notion-text-code">four</code>,
<code class="notion-text-code">five</code>,
<code class="notion-text-code">six</code>,
<code class="notion-text-code">seven</code>,
<code class="notion-text-code">eight</code>, and
<code class="notion-text-code">nine</code>
<i class="notion-text-italic">also</i> count as valid "digits".
</blockquote>
<p>
This means something like
<code class="notion-text-code">onegjdf7sdjs12</code> would result in 1 (from
the <code class="notion-text-code">one</code>) and 2 (from
<code class="notion-text-code">2</code>).
</p>
<p>
What was not mentioned though was that these numeric strings could over lap:
<code class="notion-text-code">onegjdf7sdjs1eightwo</code> would result in 1
(from <code class="notion-text-code">one</code>) and 2 (from
<code class="notion-text-code">two</code>).
</p>
<p>
I argued, to very little success in convincing others, that there are three
different ways to interpret this:
</p>
<ol class="notion-numbered_list">
<li class="notion-numbered_list_item notion-color-default">
Start from beginning until you find the first number (in this case,
<code class="notion-text-code">one</code>). Then start from the end to find
the last (<code class="notion-text-code">two</code>). Combine these two.
</li>
<li class="notion-numbered_list_item notion-color-default">
Start from beginning, convert written numbers to digits and then take the
first and last. In my example, this would have created a string of
<code class="notion-text-code">1gjdf7sdjs18wo</code> and resulted in 1 and
8.
</li>
<li class="notion-numbered_list_item notion-color-default">
Start from the end, convert written numbers to digits and then take the
first and last. In my example, this would have created a string of
<code class="notion-text-code">1gjdf7sdjs1eigh2</code> and resulted in 1 and
2.
</li>
</ol>
<p>
From a pure perspective of puzzle solving, I can see this being argued but I
do disagree that it would have been a straight-forward case.
</p>
<p>
If we look at a simpler example:
<code class="notion-text-code">eightwo</code>. How many numbers are in that
string? The case 1 argues there are two: 8 and 2. Case 2 argues there’s only 8
and case 3 argues there’s only 2.
</p>
<p>
Given the quoted instruction, I’m arguing that the only cases could be
<code class="notion-text-code">8wo</code> or
<code class="notion-text-code">eigh2</code>. Even with elves, I’d argue that
there’s no way to reach a string
<code class="notion-text-code">eightwo</code> by
<i class="notion-text-italic">spelling out digits with letters.</i>
</p>
<p>
This doesn’t take anything away from the puzzle but in general, I feel like
this was a bit sloppy, especially if this was (as I believe it to be)
intentional. I believe a good puzzle doesn’t have any tricks outside its
description. A lot of “traditional” puzzles were full of these gotchas that
required outside information and I never liked those. For me, a good puzzle is
one where the difficulty comes from inside a well-defined puzzle.
</p>
<h2 class="notion-heading_2 notion-color-default">
I like to parse input into well-defined data structures
</h2>
<p>
Many programming puzzles outside Advent of Code are created in a way where
near-optimal performance is a minimum requirement. I like Advent of Code
because it’s not a focus. Sometimes, the second part of the puzzle requires a
bit more performant solution than straight-forward solution but often you can
safely solve them with a piece of code that you would write in your normal
day-to-day programming.
</p>
<p>In day 2 for example, I created a data structure as follows:</p>
<pre class="language-python"><code class="language-python">from collections import namedtuple
Game = namedtuple('Game', ['id', 'rounds'])
Round = namedtuple('Round', ['blue', 'red', 'green']) </code></pre>
<p>
and then parsed the inputs into these
<code class="notion-text-code">Game</code>s and
<code class="notion-text-code">Round</code>s.
</p>
<p>
I did spend a bit of extra time in the parsing into data structures phase but
it made especially the second part of the puzzle rather simplistic to solve
and even more importantly, easy to read:
</p>
<pre class="language-python"><code class="language-python">def calculate_power(game):
min_green = max(rnd.green for rnd in game.rounds)
min_blue = max(rnd.blue for rnd in game.rounds)
min_red = max(rnd.red for rnd in game.rounds)
return min_green * min_blue * min_red</code></pre>
<p>
In puzzles, these are less <i class="notion-text-italic">needed </i>as the
code is run once, the scope of the code is smaller and the time investment in
the beginning can be easily argued is too much. But in real life it’s very
valuable as that investment happens usually once (with small iterative
improvements) and it gets used and referenced in the code base over and over
again.
</p>
<h2 class="notion-heading_2 notion-color-default">
<code class="notion-text-code">re.finditer</code> finds values and their
locations
</h2>
<p>
So many good things in early days. In day 3, after using other methods and
then discussing it with friends, I discovered that Python’s regular expression
library has method
<a href="https://docs.python.org/3/library/re.html#re.finditer" class="notion-text-href">finditer</a>
that would have been really handy for this.
</p>
<pre class="language-python"><code class="language-python">import re
line = '...345....44..'
numbers = re.finditer(r'(\d+)', line)
for number in numbers:
print(f'''
Number: {number} starts
at index {number.start()}
and has {len(number.groups(0)[0])} digits.
''')</code></pre>
<p>I should use it more!</p>
<h2 class="notion-heading_2 notion-color-default">
Python defaulting to iterator-based functions was a great move
</h2>
<p>
Python’s move from major version 2 to 3 was not the smoothest operation and
caused quite a bit of hassle. Looking back now, 15 years later, I’m happy we
made it.
</p>
<p>
<a href="https://docs.python.org/3/whatsnew/3.0.html#views-and-iterators-instead-of-lists" class="notion-text-href">One change that was made</a>
was to move from <code class="notion-text-code">range/xrange</code> style
double functions to dropping the extra functions and making the base functions
(like <code class="notion-text-code">range</code> and
<code class="notion-text-code">zip</code>) return iterators instead of lists.
</p>
<p>
Personally, I was way more junior 15 years ago than I am now and it didn’t
feel like this was big back then. This December I realized how great this was.
Since moving to 3.0, I haven’t had to think about these at all.
</p>
<p>
Last month I was reading through some PHP solutions where the code read
exactly the same on the surface as my Python solutions but they ran into
memory issues due to the arrays/lists being looped over grew too big. With
Python 3.10, I never had to think about it once because so much of standard
library returns a generator and “just works”.
</p>
<h2 class="notion-heading_2 notion-color-default">
Making connections between available tools and abstract problem settings
</h2>
<p>
On the seventh day, I solved the Camel Cards game puzzle by relying on muscle
memory. I’ve written similar code for poker hands so many times during my
studies and teaching. I used classes with inheritance and wrote individual
functions for each different hand.
</p>
<p>
When I saw
<a href="https://github.com/lamperi/aoc/blob/main/2023/7/solve.py#L31" class="notion-text-href">Toni’s solution</a>
that used <code class="notion-text-code">Counter.most_common</code> and
pattern matching, I was at awe:
</p>
<pre class="language-python"><code class="language-python">def hand_score(hand):
cards, _ = hand
scores = [score(c) for c in cards]
cnt = Counter(cards)
match cnt.most_common():
case [(_, 5)]: return (Hand.FIVE_OF_A_KIND, scores)
case [(_, 4), _]: return (Hand.FOUR_OF_A_KIND, scores)
case [(_, 3), (_, 2)]: return (Hand.FULL_HOUSE, scores)
case [(_, 3), _, _]: return (Hand.THREE_OF_A_KIND, scores)
case [(_, 2), (_, 2), _]: return (Hand.TWO_PAIR, scores)
case [[_, 2], _, _, _]: return (Hand.ONE_PAIR, scores)
case [_, _, _, _, _]: return (Hand.HIGH_CARD, scores)</code></pre>
<p>
I think this function is so beautiful. Each different case is solved with just
one pattern case.
</p>
<p>
I used <code class="notion-text-code">Counter</code> myself too and I knew
<code class="notion-text-code">most_common()</code> exists and how it works
but it did not even pop to my mind that I could use it to solve this
particular problem.
</p>
<p>
To become better at these is mostly a question of experience with different
kinds of problems and different kinds of tools.
</p>
<h2 class="notion-heading_2 notion-color-default">
The negative side of Advent of Code
</h2>
<p>
Advent of Code is not all just roses and fun times. Challenging puzzles and
thriving community can lead to us putting too much pressure on ourselves and
getting lost in the journey. Two years ago, Advent of Code was a way for me to
completely drown myself into something when I was on a burnout sick leave and
didn’t want to worry about real life.
</p>
<p>
Zoe Aubert’s blog post
<a href="https://zoeaubert.me/blog/advent-of-code-is-not-healthy-for-me/" class="notion-text-href">Advent of Code is not healthy for me</a>
had a lot of thoughts that resonated very much with me. I sometimes struggle
with coming to terms with the fact that I can’t solve all the puzzles. Which
is weird because I know that I’m not a great developer and especially not
great at puzzles so the lower expectations don’t match my emotional response
to it.
</p>
<p>
My impostor syndrome also kicks in often with Advent of Code. I write open my
puzzle solutions and sprinkle in educational Python and puzzle solving content
so when I fail to even solve a puzzle, I feel like such a fraud: “Who am I to
teach anything when I can’t even solve these all myself”. I know it’s not like
that and explaining the things I know and can solve is a net positive even if
I can’t reach 100% of the stars.
</p>
<p>
And while in life I’ve gotten much better at not trying to complete everything
I start, with Advent of Code, due to its social and community aspect, the
threshold do make that decision is higher than it should be. This year, it
hasn’t (yet) been an issue but in the past few years, I think I’ve pushed a
bit too much at the expense of my mental health.
</p>
<h2 class="notion-heading_2 notion-color-default">
Other people’s solutions I enjoyed
</h2>
<p>
During the December, after finishing my own solutions, I read solutions and
explanations of bunch of other developers who wrote them down and published.
Here are some and the languages they used to solve puzzles.
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
Zoe Aubert -
<a href="https://zoeaubert.me/tags/advent-of-code/" class="notion-text-href">https://zoeaubert.me/tags/advent-of-code/</a>
( Rust )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Robb Knight -
<a href="https://rknight.me/blog/tags/adventofcode/" class="notion-text-href">https://rknight.me/blog/tags/adventofcode/</a>
( PHP )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Lewis D -
<a href="https://lewisdale.dev/" class="notion-text-href">https://lewisdale.dev/</a>
( Typescript )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Eric Burden -
<a href="https://www.ericburden.work/categories/advent-of-code-2023/" class="notion-text-href">https://www.ericburden.work/categories/advent-of-code-2023/</a>
( Kotlin )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Neil Smith -
<a href="https://work.njae.me.uk/tag/advent-of-code/" class="notion-text-href">https://work.njae.me.uk/tag/advent-of-code/</a>
( Haskell )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Dr Lee A. Christie -
<a href="https://github.com/leechristie/advent-of-code-2023/blob/main/logbook/README.md" class="notion-text-href">https://github.com/leechristie/advent-of-code-2023/blob/main/logbook/README.md</a>
( 8 languages: C, C#, Go, Java, Python, Rust, Swift, Typescript )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Bogdan Popa -
<a href="https://defn.io/" class="notion-text-href">https://defn.io/</a> (
Racket )
</li>
<li class="notion-bulleted_list_item notion-color-default">
Ella Kaye -
<a href="https://adventofcode.ellakaye.co.uk/2023" class="notion-text-href">https://adventofcode.ellakaye.co.uk/2023</a>
( R )
</li>
<li class="notion-bulleted_list_item notion-color-default">
David Brownman -
<a href="https://advent-of-code.xavd.id/writeups/2023/" class="notion-text-href">https://advent-of-code.xavd.id/writeups/2023/</a>
( Python )
</li>
</ul>
<h2 class="notion-heading_2 notion-color-default">
I had a great time this year
</h2>
<p>
All in all, 2023 was a great year with Advent of Code for me. The puzzles
provided a nice challenge and a brewing ground for discussions without being
too much. I usually took a few hours in the morning to go through them,
writing code, then writing explanations and doing a few rounds of refactoring.
</p>
Year in Review 2023
2023-12-27T00:00:00Z
https://hamatti.org/posts/year-in-review-2023/
<h2 class="notion-heading_2 notion-color-default">Introduction</h2>
<p>
Welcome to my annual Year in Review blog post. I mainly write these for myself
but like to publish them on my blog because I enjoy reading other people’s
similar posts.
</p>
<p>
To prepare for these, I use a really cool booklet called
<a href="https://yearcompass.com/" class="notion-text-href">Year Compass</a>
that provides nice prompts to take a look at the previous year and plan for
the next.
</p>
<h2 class="notion-heading_2 notion-color-default">Previous years</h2>
<p>
<a href="https://hamatti.org/posts/year-in-review-2016" class="notion-text-href">2016</a>,
<a href="https://hamatti.org/posts/year-in-review-2017" class="notion-text-href">2017</a>,
<a href="https://hamatti.org/posts/year-in-review-2018" class="notion-text-href">2018</a>,
<a href="https://hamatti.org/posts/year-in-review-2019" class="notion-text-href">2019</a>,
<a href="https://hamatti.org/posts/year-in-review-2020" class="notion-text-href">2020</a>,
<a href="https://hamatti.org/posts/year-in-review-2021/" class="notion-text-href">2021</a> and
<a href="https://hamatti.org/posts/year-in-review-2022" class="notion-text-href">2022</a>
</p>
<h2 class="notion-heading_2 notion-color-default">
What I hoped for 2023 last year?
</h2>
<p>
Let’s start by seeing how I did with this year in regard to my hopes from 12
months ago:
</p>
<blockquote class="notion-quote notion-color-default">
I want to focus on my physical and mental health that I've been sacrificing
over the past 5-6 years while chasing success and battling the burden of the
pandemic. I want to get healthier and get a better grasp of the day-to-day as
that's been my main challenge lately.
</blockquote>
<p>
While this journey is a long one and still going, I definitely succeeded here.
In the beginning of the year, I was extremely exhausted, burnt out and
demotivated which led me to leave a dream job, move back home and restructure
my life.
</p>
<p>
It took 8 long months but finally as the summer turned into fall, my health
took a major turn into better and I’ve been enjoying life this fall a lot.
</p>
<blockquote class="notion-quote notion-color-default">
I do want to make progress with my writing this year as well.
</blockquote>
<p>
Almost 100 blog posts this year and I ended up writing more varied content and
am really happy that I got into a habit of writing blog posts that participate
in the discussion in blogosphere.
</p>
<p>
I participated in
<a href="https://hamatti.org/posts/blaugust-2023-here-we-come/" class="notion-text-href">Blaugust challenge</a>
in August and that really tested my skills as a writer and also inspired a
bunch of new things.
</p>
<blockquote class="notion-quote notion-color-default">
I'm launching a new newsletter for software developers next week!
</blockquote>
<p>
<a href="https://syntaxerror.tech/" class="notion-text-href">Syntax Error</a>
was launched in January and over the first year, I sent 10 newsletter issues
and by now, have over 200 email subscribers and likely even more RSS readers.
It got me more excited about debugging and my Debugging Python talk was ever
so much better this year thanks to it.
</p>
<h2 class="notion-heading_2 notion-color-default">General thoughts of 2023</h2>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/1.png" alt="A greyscale photo of a wet concrete floor with bright footprints coming towards the camera " />
<p>To put it mildly, it was yet another very interesting and eventful year.</p>
<p>
Beginning of the year was all about restructuring my life. I
<a href="https://hamatti.org/posts/im-leaving-mozilla/" class="notion-text-href">left Mozilla and Berlin</a>, moved
<a href="https://hamatti.org/posts/a-quick-life-update/" class="notion-text-href">back home to Turku and got a new job</a>. I was very tired but little by little, things started to get better.
</p>
<p>
Throughout spring and summer, I worked writing Javascript and PHP and
occasionally playing board games and Pokemon TCG with friends. There were a
few good moments but mostly it was real rough. I was constantly tired and had
many moments of self-doubt.
</p>
<p>
Towards the fall, I started feeling better and even got back to conference
speaking as I visited PyCon CZ and PyCon Sweden. And then things took a turn
at work and I ended up spending the last few months unemployed running
communities, planning new ones and doing a lot of introspection.
</p>
<p>
My feeling of the year was that I hadn’t done that much. But after doing this
year’s retrospection and writing of this review, I did manage to get quite a
few good things done. And that’s one reason why I like to do these kind of
reviews of the year, to remind myself that good things happen even if some
days feel dark.
</p>
<h2 class="notion-heading_2 notion-color-default">Writing</h2>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/2.png" alt="A year calendar graph with each day as a square. Most of the Wednesdays of the year are marked, as well as every day in August. In addition, a few squares randomly here and there across the year are marked. " />
<p>
If you’re a returning reader, you probably have noticed writing is a big
passion of mine. This year I wrote 79 blog posts in this blog plus 10 issues
of Syntax Error. That is a new personal record and I’m happy with the
increased quality of my blog posts and how that has made them easier to write
than ever.
</p>
<p>
In August I participated in Blaugust for the first time and to my surprise,
<a href="https://hamatti.org/posts/blaugust-2023-lessons-learned/" class="notion-text-href">managed to write 31 blog posts in 31 days to achieve Diamond Rainbow
Award</a>.
</p>
<p>Some of my personal favorites from my writing this year:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/guide-to-landing-your-first-dev-job/" class="notion-text-href">Guide to landing your first dev job (Jan 2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/organizing-a-mess-with-cherry-picking/" class="notion-text-href">Organizing a mess with cherry-picking (Mar 2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/writing-documentation-is-a-great-tool-to-improve-software-quality/" class="notion-text-href">Writing documentation is a great tool to improve software quality (May
2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/give-your-commands-consistent-names/" class="notion-text-href">Give your commands consistent names (Jun 2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/blog-comments-via-mastodon/" class="notion-text-href">Blog comments via Mastodon (Jul 2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/website-rewrite-and-switching-to-notion-as-cms/" class="notion-text-href">Website rewrite and switching to Notion as CMS (Aug 2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/showing-most-popular-posts-with-netlify-analytics/" class="notion-text-href">Showing most popular posts with Netlify Analytics (Oct 2023)</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/done-is-the-engine-of-more/" class="notion-text-href">Done is the engine of more (Nov 2023)</a>
</li>
</ul>
<h2 class="notion-heading_2 notion-color-default">
Communities, events and speaking
</h2>
<p>
For most part of the year I took a near complete break from all things
community. I still managed to make few things happen.
</p>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/3.png" alt="A collage of 7 selfies of Juhis wearing a Santa hat with various other people holding boxes of chocolate and Christmas cards. In the bottom right, a text “Merry Christmas from Turku Loves Frontend” " />
<p>
With
<a href="https://turkufrontend.fi/" class="notion-text-href">Turku ❤️ Frontend</a>
we organized 9 meetups throughout the year and after moving back to Turku I
had the opportunity to participate regularly as well. We celebrated our 50th
event in May and I was happy to see a good balance of new people and old
regulars visit the events throughout the year.
</p>
<p>
I also got to return to my favorite tradition (pictured above) where I visit
all the partnering companies’ offices right before Christmas and spread happy
Christmas spirit and thank the partners for the collaboration. So many nice
discussions spark from those visits every year and I like the small gesture.
</p>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/4.png" alt="A conference stage with Tuuli standing on the left end, Jani on the right end and a slide with Future Frontend 2023 logo in the middle. " />
<p>
In June, we organized
<a href="https://futurefrontend.com/2023/" class="notion-text-href">Future Frontend</a>
conference that was our pivot from React to new topics. The event went really
nice and I had a great time making new friends and learning new things. I
wrote
<a href="https://hamatti.org/posts/future-frontend-2023-recap/" class="notion-text-href">a recap of it in my blog</a>
and our next year’s conference is coming in June again with
<a href="https://futurefrontend.com/#buy-tickets" class="notion-text-href">ticket sales on-going right now</a>. Come to Helsinki, it’s gonna be fun and it’s the best time of the year to
visit the country.
</p>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/5.png" alt="Juhis standing on stage next to a speaker podium. Behind him, a slide that has a raised hand and a text “Raise your hand if you’ve ever written a bug!” " />
<p>
I got to
<a href="https://hamatti.org/posts/traveling-europe-on-land-turku-to-prague/" class="notion-text-href">travel to Prague</a>
for
<a href="https://cz.pycon.org/2023/" class="notion-text-href">PyCon CZ</a> as
a backup speaker and got to do a lightning talk about version numbers in
September. In October I spoke to engineering students at Turku University of
Applied Sciences about communities, networking and developer culture. In
November, I traveled to
<a href="https://www.pycon.se/" class="notion-text-href">PyCon Sweden</a> to
speak about debugging Python (pictured above) and did my version number
lightning talk as well. I finished the year by doing that same lightning talk
in our Turku ❤️ Frontend meetup in December.
</p>
<p>
In November, me and Dan sat down one Friday and founded
<a href="https://archipylago.dev/" class="notion-text-href">archipylago</a>, a
new Python community for local developers in Turku with first events coming up
in January 2024. (If you’re a Python developer in Finland, sign up to
<a href="https://meetabit.com/events/archipylago-1-january-2024-at-valohai" class="notion-text-href">our first event Thu Jan 11th</a>). Or if you’re a Python dev in Stockholm or Tallinn and wanna travel, why
not come do a talk in one of our events? (Unfortunately we don’t have budget
to pay for trips/speaking fee.)
</p>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/6.png" alt="A 3-pane Lord of the Rings meme. First pane: Aragorn says “You have my sword”. Second pane: Legolas says “And my bow”. In the last pane, a yellow rubber duck pasted over Gimli’s face with text “And my duck!” " />
<p>
<a href="https://www.syntaxerror.tech/" class="notion-text-href">Syntax Error</a>
got its first 10 newsletters and bit over 200 email subscribers. It brought a
lot of joy and fun moments to my life and as mentioned above, it helped me
deepen my understanding and skills in debugging and helped make my debugging
talk way better. On the other hand, I had many moments when I felt like the
growth of subscriber base was very slow compared to many other newsletters
that I follow. I know I shouldn’t compare to others but it does make me wonder
why I’m so bad at marketing things.
</p>
<p>
Given that I dropped pretty much all my projects in January to focus on
recovering, I pretty much picked everything up again and added a few as the
year went by. I do feel a lot better but I do need to ask myself, why do I do
this to myself every time.
</p>
<h2 class="notion-heading_2 notion-color-default">Projects</h2>
<h3>Software</h3>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/7.png" alt="A contribution graph from GitHub showing 1150 contributions in 2023, rather equally distributed throughout the year with a notable gap in late January and February " />
<p>
In addition to spending 7 months as a software developer writing React for a
SaaS platform and bit of PHP for a WordPress plugin, I continued maintaining
the few projects I have and working on one new project. I describe myself as a
“Saturday evening developer” who “enjoys writing small apps for demos,
tutorials and talks”.
</p>
<p>
My
<a href="https://github.com/hamatti/" class="notion-text-href">GitHub profile</a>
says I’ve made a bit over 1000 contributions in 2023. It’s more than I
expected but some of it is also content since since August, my blog posts have
gone through GitHub.
</p>
<h4>235</h4>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/8.png" alt=" " />
<p></p>
<p>
For my NHL results CLI tool 235 I released version
<a href="https://hamatti.github.io/nhl-235/release-notes/1.3.0" class="notion-text-href">1.3.0</a>
that added a feature allowing users to see goals and assists of their favorite
players. It was interesting to get back to
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/" class="notion-text-href">writing Rust after a year</a>. It continues to be the tool I use every single day and I love it.
</p>
<h4>Pokemon apps</h4>
<p>
As new Scarlet & Violet Pokemon TCG sets rolled in throughout the year, I kept
maintaining
<a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator" class="notion-text-href">Gym Leader Challenge Decklist Validator</a>
and
<a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/" class="notion-text-href">Pokemon TCG Card Viewer Firefox extension</a>. I got a single 5 star review for the Firefox extension!
</p>
<p>
I also started work on a deck builder that I have
<a href="https://www.youtube.com/watch?v=_PGJaQVZjuQ" class="notion-text-href">a sneak peek demo</a>
of its main feature that streamlines picking of evolution lines. The app is
still very early in development but maybe in 2024 I’d be able to release the
first version. I use it myself every time I want to build a deck so it works
and is way better than the official Pokemon TCG Live’s deck builder but it’s
still missing a few features I want to add before release and probably will
rewrite the code entirely as its gotten messy during prototyping.
</p>
<h4>Advent of Code</h4>
<p>
To finish the year, I once again took part in
<a href="https://adventofcode.com/" class="notion-text-href">Advent of Code</a>,
<a href="https://hamatti.org/adventofcode/2023" class="notion-text-href">solving problems with Python and writing educational explanations along the
way</a>. Next week, I’ll share my retrospective of this year’s experience. By
Christmas, I reached 36 stars (straight out skipped days 20+ due to Christmas
stuff, will maybe revisit them during the holidays) and my explanations
gathered a good ~25 000 views on my website during the month.
</p>
<h4>This website</h4>
<p>
During the year I also made a lot of smaller improvements and
<a href="https://hamatti.org/posts/website-rewrite-and-switching-to-notion-as-cms/" class="notion-text-href">one complete rewrite to this very website</a>. I switched the blog CMS from Ghost to Notion and I’ve been very happy with
that, even though there are a few things to still fix and improve. While doing
that, I added
<a href="https://hamatti.org/posts/blog-comments-via-mastodon/" class="notion-text-href">blog comments via Mastodon</a>,
<a href="https://hamatti.org/posts/custom-cookie-consent-for-video-embeds/" class="notion-text-href">custom cookie consent flow for embedded Youtube videos</a>
and
<a href="https://hamatti.org/posts/showing-most-popular-posts-with-netlify-analytics/" class="notion-text-href">most popular posts</a>.
</p>
<p>
Right before Christmas I also added
<a href="https://hamatti.org/blog/stats/" class="notion-text-href">a statistics page for my blog</a>
and learned two things: I’m very commited to publishing every Wednesday and
that out of the last 168 weeks, I’ve published a blog post in 152 of them.
</p>
<h3>Community projects</h3>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/9.png" alt="A dot graph of work experience in years on x axis and annual salary in y axis. Dots are scattered all around with a slight trend upwards as experience grows. " />
<p>
With our Finnish developer community
<a href="https://koodiklinikka.fi/" class="notion-text-href">Koodiklinikka</a>
I orchestrated and ran
<a href="https://koodiklinikka.github.io/palkkakysely/2023/" class="notion-text-href">our annual salary survey with nearly 1000 respondents this year.</a>
It was a lot of fun as I created many memes to promote it. I’m very proud of
the result as while it has its own biases and the data cannot be extrapolated
to represent entire Finnish IT sector, it offers developers a lot of good
insights into what people are making for which kinds of roles.
</p>
<h3>Potluck board game design</h3>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/10.png" alt=" " />
<p>
In the spring, I got my hands on
<a href="https://hamatti.org/tabletop/potluck" class="notion-text-href">Potluck</a>, my board game project and it was a magical moment to hold something
tangible and physical I had created as I usually only dabble in the digital
realm. And it’s been a great thing to have: I’ve played so many games with so
many people in so many different places from pubs to trains to conferences.
</p>
<p>
It has been a discussion starter in many occasions too. People have been
interested in the process, the design and the final product and I’ve had so
many lovely discussions about board game design thanks to it. I have an itch
to start working on the next project in the spring.
</p>
<h2 class="notion-heading_2 notion-color-default">
Movies, games, books, podcasts and so on
</h2>
<p>
I watched a metric ton of movies and TV shows, and mainly
<a href="https://hamatti.org/posts/lets-talk-about-steam-deck/" class="notion-text-href">thanks to Steam Deck</a>
played a lot of games this year. Here are a few picks of recommendation. Some
were released this year and some I happened to watch, listen or play this
year.
</p>
<h3>Games</h3>
<p>
What a year it was for gaming. Great new releases and because of Steam Deck,
visited many old favorites.
</p>
<h4>Switch</h4>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/11.png" alt="Nintendo Switch recap with 9 total games and 78 total hours played this year. Game art shown around from various different games. " />
<p>
Game of the Year was obviously
<a href="https://zelda.nintendo.com/tears-of-the-kingdom/" class="notion-text-href">The Legend of Zelda: Tears of the Kindgom</a>
for Switch. It improved on the already great Breath of the Wild and brought in
plenty of new ideas. I spent 45 hours of my total 78 hours in Hyrule.
</p>
<p>
I haven’t played Super Mario Bros. Wonder yet so Zelda was pretty much the
only new Switch game I played this year. I did buy Advance Wars 1+2 Bootcamp
but more of that below.
</p>
<p>
I’m not sure why FIFA 20 and Picross S6 are not showing up on this recap since
I spent a good chunk of time in January and February playing FIFA when I was
waiting to find an apartment after moving back to Finland.
</p>
<h4>Playstation</h4>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/12.png" alt="PS4 Wrap-up top 5 games screen with NHL 21 (134 hours), Far Cry 6 (37 hours), Far Cry 5 (26 hours), Call of Duty: Black Ops Cold War (14 hours) and Madden NFL 20 (8 hours) " />
<p>
PS4 Wrap-up revealed that 58% of the time on Playstation I spent with NHL 21 -
all of it on a single Franchise mode with NY Rangers. After that, a main chunk
was spent on Far Cry 6, followed by Far Cry 5, both great additions to the
series. I also spent a nice portion playing Madden 20 and FIFA 23.
</p>
<p>
The Wrap-up claims Call of Duty taking 14 hours but roughly 10 hours of that
was it sitting on the main menu, waiting to download the single player
campaign so it’s not very accurate.
</p>
<p>
Pretty much the only reason I still have PS4 is the sports games. NHL, FIFA
and Madden – and before 2k went and made their NBA games unplayable, NBA 2k21
– require either Playstation or Xbox pretty much.
</p>
<h4>Steam Deck / Mac</h4>
<img src="https://hamatti.org/assets/img/posts/year-in-review-2023/13.png" alt="Steam Replay collection with 44 different game covers " />
<p>
On Steam Deck, I really enjoyed
<a href="https://store.steampowered.com/app/1562430/DREDGE/" class="notion-text-href">DREDGE</a>, a horror fishing simulator. In terms of number of hours, the most played
game of the year was for the second year in the running
<a href="https://store.steampowered.com/app/413150/Stardew_Valley/" class="notion-text-href">Stardew Valley</a>
(42% of playtime on Steam). I started running out of power to play it with
<a href="https://hamatti.org/posts/stardew-valley-mods-i-use/" class="notion-text-href">all my mods</a>
on my Macbook but with Steam Deck, I was able to keep on going.
</p>
<p>
Deck builders are great games and
<a href="https://store.steampowered.com/app/646570/Slay_the_Spire/" class="notion-text-href">Slay the Spire</a>
kept the top spot of that genre this year and I reached many ascensions during
my trips and wait times in train stations and ferry terminals. I tested quite
a few new indie games of the genre but only one managed to capture my
attention:
<a href="https://store.steampowered.com/app/1970580/Backpack_Hero/" class="notion-text-href">Backpack Hero</a>. I originally played the demo via Itch.io, then got the early access on
Steam and finally at the end of 2023 they released 1.0 with a story mode and
lots of new stuff and that finally got me hooked. It’s a really fun take on
the genre by turning the deck into a backpack inventory management puzzle but
keeping the rogue-like format. Backpack Hero ended up being my 3rd most played
game on Steam this year, right behind Stardew Valley and Slay the Spire.
</p>
<p>
For the relaxed sessions,
<a href="https://store.steampowered.com/app/1455840/Dorfromantik/" class="notion-text-href">Dorfromantik</a>
and
<a href="https://store.steampowered.com/app/1629520/A_Little_to_the_Left/" class="notion-text-href">A Little to the Left</a>
filled the void. They are great games for when you don’t care about performing
or winning but just want to have a great time.
</p>
<p>
As a big Advance Wars fan, this year had a lot of potential.
<a href="https://www.nintendo.com/us/store/products/advance-wars-1-plus-2-re-boot-camp-switch/" class="notion-text-href">Advance Wars 1+2: Re-Boot Camp</a>
was released for Switch in the spring and its spiritual successor Wargroove
got its own sequal
<a href="https://store.steampowered.com/app/1346020/Wargroove_2/" class="notion-text-href">Wargroove 2</a>. Unfortunately, I got disappointed by both. The Advance Wars game didn’t
bring really anything new to the table (the GBA version is just snappier to
play with so I played more of that) and Wargroove 2 took a turn towards more
character-focused story elements and I lost interest on it quite early on.
</p>
<p>
Luckily, romhacking scene is strong and I got to keep my Advance Wars desires
alive with
<a href="https://www.romhacking.net/hacks/6012/" class="notion-text-href">Advance Wars Returns</a>
from 2021. This year saw also the release of two really great romhacks:
<a href="https://www.romhacking.net/hacks/8134/" class="notion-text-href">Pokemon Crystal Legacy</a>
that fixes a lot of balance issues and makes the game more challenging and
interesting. In Mario Kaizo scene, BarbarousKing released long-awaited and
brutally difficult
<a href="https://www.smwcentral.net/?p=viewthread&t=126133" class="notion-text-href">Grand Poo World 3</a>. If I ever complete even one level of that game, I’d consider myself a top
tier gamer.
</p>
<p>
Final mention goes to
<a href="https://store.steampowered.com/app/1244090/Sea_of_Stars/" class="notion-text-href">Sea of Stars</a>, a great modern turn-based RPG. I bought it right before my trip to Czechia
and ended up playing a ton of it during the trip as I had long waits with no
Internet in ferries, trains and terminals. It has a captivating story and game
play that combines great features from many older games in the genre into a
modern mix with good playability.
</p>
<p>
While Steam’s Replay doesn’t say how many hours I’ve played, I calculated
through the percentage shares that I got 300 hours of recorded Steam time.
Plus all the emulator games, offline gaming, GOG and Epic Store games so let’s
say 50-100 hours on top.
</p>
<p>
With all three platforms combined, that means 600-700 hours of gaming this
year. And I had such a good time with almost every of those hours.
</p>
<h3>Movies & TV</h3>
<p>
When writing this, I noticed how bad I am keeping track of or remembering what
I’ve watched. So I’m missing out a ton of great stuff but here are some
highlights of what I remember.
</p>
<p>
Marvel Cinematic Universe kept churning out good stuff.
<a href="https://www.imdb.com/title/tt10954600/" class="notion-text-href">Ant-Man and the Wasp: Quantumania</a>,
<a href="https://www.imdb.com/title/tt6791350/" class="notion-text-href">Guardians of Galaxy 3,</a>
<a href="https://www.imdb.com/title/tt9140554/" class="notion-text-href">Loki Season 2</a>
and
<a href="https://www.imdb.com/title/tt9114286/" class="notion-text-href">Black Panther: Wakanda Forever</a>
were all movies and shows I enjoyed a ton.
</p>
<p>
Star Wars brought
<a href="https://www.imdb.com/title/tt8111088/" class="notion-text-href">The Mandalorian</a>
and
<a href="https://www.imdb.com/title/tt13622776/" class="notion-text-href">Ahsoka</a>
to the table and I’m a sucker for all things Star Wars so I ate up every
episode and geeked out hard. I still haven’t seen Bad Batch’s latest season
from this year but it’s on my ever-growing to-watch list as the first season
was real good.
</p>
<p>
<a href="https://www.imdb.com/title/tt9362722/" class="notion-text-href">Spider-Man: Across the Spider-Verse</a>
was a stunner.
<a href="https://www.imdb.com/title/tt5635026/" class="notion-text-href">Peter Pan & Wendy</a>
was a big positive surprise.
<a href="https://www.imdb.com/title/tt15789038/" class="notion-text-href">Elemental</a>
wasn’t history’s best Pixar film but an entertaining one anyway.
</p>
<p>
<a href="https://www.imdb.com/title/tt11080108/" class="notion-text-href">The Map of Tiny Perfect Things</a>
was maybe my favourite movie I watched all year.
</p>
<p>
British comedy & game shows were great as usual.
<a href="https://www.channel4.com/programmes/taskmaster" class="notion-text-href">Taskmaster UK’s 16th season</a>
was an upper mid tier season within the show,
<a href="https://www.bbc.co.uk/programmes/b00lskhg" class="notion-text-href">Only Connect’s 19th series</a>
featured great matches and questions,
<a href="https://www.imdb.com/title/tt29054468/" class="notion-text-href">Alan Carr’s Picture Slam</a>
had a few weird quirks but otherwise was a ton of fun and
<a href="https://www.imdb.com/title/tt7316998/" class="notion-text-href">Richard Osman’s House of Games</a>
completed the my need for quizzes.
</p>
<p>
<a href="https://www.imdb.com/title/tt19889996/" class="notion-text-href">Everyone Else Burns</a>
left me wanting for more. It was absolutely hilarious British comedy and I
can’t wait to see the second season when it comes out.
</p>
<p>
From Finnish media, a few good mentions:
<a href="https://areena.yle.fi/1-63837104" class="notion-text-href">Pohjoisen Tähti</a>
was a stunning Lapland cop series with boxing twist and a stellar crew. Third
season of
<a href="https://www.imdb.com/title/tt6800294/episodes/?season=3" class="notion-text-href">Ivalo (Artic Circle)</a>
came out and also it is a Lapland cop series with a stellar crew. There’s
clearly something in the water.
<a href="https://areena.yle.fi/1-50831169" class="notion-text-href">Etsijät</a>
took a moment to get started but ended up being a really fun series. I also
discovered
<a href="https://areena.yle.fi/1-50254195" class="notion-text-href">Pientä laittoa</a>
this year, it’s a real fun comedy series about friendship.
</p>
<p>
FInland also got its own version of a great Swedish quiz show
<a href="https://sv.wikipedia.org/wiki/P%C3%A5_sp%C3%A5ret" class="notion-text-href">På spåret</a>, called
<a href="https://areena.yle.fi/1-62829974" class="notion-text-href">Hengaillaan</a>
and it was really really good.
</p>
<h3>Podcasts</h3>
<p>
Tom Scott’s quiz podcast
<a href="https://lateralcast.com/" class="notion-text-href">Lateral</a> made
so many moments of waiting or traveling go by way faster. It’s a really great
concept, although I really hope they’d keep producing it as a video show as
they did with the first few episodes.
</p>
<p>
In tech podcast scene, I mainly enjoyed
<a href="https://koodarikuiskaaja.fi/" class="notion-text-href">Elisa Heikura’s Koodarikuiskaaja (in Finnish)</a>, <a href="https://syntax.fm/" class="notion-text-href">Syntax.fm</a> and
<a href="https://www.frontendhappyhour.com/" class="notion-text-href">Front End Happy Hour</a>.
</p>
<h3>Books</h3>
<p>
With books, I did a horrible job at finishing any. But I did start and
continue quite a few.
<a href="https://www.goodreads.com/en/book/show/7155145" class="notion-text-href">Seth Godin’s Linchpin</a>
had a lot of good food for thought wrapped into a very sethgodin-y hustle
culture that made me want to skip some parts of the book.
</p>
<p>
I listened to
<a href="https://www.waterstones.com/book/strong-female-character/fern-brady/9781914240447" class="notion-text-href">Fern Brady’s Strong Female Character</a>
as an audio book and liked it a lot. I enjoy biography books that talk about a
life completely different from mine. It’s eye opening to explore other worlds
and lifes.
</p>
<p>
I revisited
<a href="https://www.goodreads.com/book/show/37570605-company-of-one" class="notion-text-href">Paul Jarvis’ Company of One</a>
as I was once again thinking about starting my own business. In the fall I
also read most of
<a href="https://www.mountaingoatsoftware.com/books/agile-estimating-and-planning" class="notion-text-href">Mike Cohn’s Agile Estimation and Planning</a>
which was a really good and practical book.
</p>
<p>
And I continued reading
<a href="https://www.goodreads.com/en/book/show/22733729" class="notion-text-href">Becky Chambers’ The Long Way to a Small, Angry Planet</a>
which is a wonderful scifi book that I’ve been reading for way over a year
now. And it’s part of a series so I hope I’d make time to actually finish it
and continue the story with the next books.
</p>
<p>
I don’t know which category to put it but I also listened to Finnish story
<a href="https://areena.yle.fi/podcastit/1-65880497" class="notion-text-href">Rakastitko, Vesku?</a>
which is a great radio/audio play about the life of Finnish multitalented
artist
<a href="https://en.wikipedia.org/wiki/Vesa-Matti_Loiri" class="notion-text-href">Vesa-Matti Loiri</a>. Also a recommendation to similar style play
<a href="https://areena.yle.fi/podcastit/1-4447708" class="notion-text-href">Itkisitkö onnesta? Tarina Gösta Sundqvistista</a>
that is even better than the Vesku story.
</p>
<h2 class="notion-heading_2 notion-color-default">What’s coming in 2024?</h2>
<p>I have four main goals for the following year.</p>
<p>
First, I’d like to <b class="notion-text-bold">find a job I can thrive in</b>.
This year especially was quite a rollercoaster in work life and had me spend
way too much in self-doubt as I failed in not just one but two jobs in the
same year.
</p>
<p>
Second, I want to <b class="notion-text-bold">build </b><b class="notion-text-bold"><a href="https://archipylago.dev/" class="notion-text-href">archipylago</a></b><b class="notion-text-bold">
into a lovely, warm and welcoming Python community</b>
for everyone interested in Python in Turku area. In a longer run, I’d like to
see it lead into bringing back PyCon Finland but not yet in 2024.
</p>
<p>
I want to
<b class="notion-text-bold">read more and actually finish some books</b>. So
I’m setting a goal of 10 books (and I count finishing that Becky Chambers book
I’ve been reading for a long time). My thinking is reading a book every month
and then knowing I won’t quite read that much in reality.
</p>
<p>
And to continue improving as a writer, I want to
<b class="notion-text-bold">write 100 blog posts + newsletter issues</b> in
2024. Towards the end of the 2023 I had multiple weeks of 2 posts per week and
if I can maintain that motion into 2024, it’s a possibility.
</p>
<p>
A bonus inspiration more than a goal is that I want to spend some time and
actually create a prototype for a board game I’ve been brainstorming inside my
mind.
</p>
<p>
The start of 2021, 2022 and 2023 have all been under horrible circumstances
but going into 2024, I’m actually feeling great and even more optimistic than
usual.
</p>
<p></p>
Merry Christmas!
2023-12-20T00:00:00Z
https://hamatti.org/posts/merry-christmas-2023/
<p>
The year is near its end and it’s time to take a break from all the things and
spend the Christmas time with the family.
</p>
<p>
If you’ve been part of my year in any way, thank you. It’s been an interesting
one and everything in it was made better because of the people around me.
</p>
<p>
Next week in the blog, I’ll wrap up the year with my annual Year in Review
post and then we’re all good to head over to 2024.
</p>
Syntax Error #10: Debugging knowledge base
2023-12-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-10-debugging-knowledge-base/
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this December issue of Syntax Error I share
a thought experiment of building a debugging knowledge base within your team.
Read full article at
<a href="https://www.syntaxerror.tech/syntax-error-10-debugging-knowledge-base/">syntaxerror.tech/syntax-error-10-debugging-knowledge-base/</a>
and either subscribe to the email or RSS feed to catch all of them.
Two months with Obsidian
2023-12-13T00:00:00Z
https://hamatti.org/posts/two-months-with-obsidian/
<p>
I love taking notes. I’m a note-taking geek. In the beginning of the year, I
wrote about
<a href="https://hamatti.org/posts/the-imperfect-mess-of-note-taking/" class="notion-text-href">my imperfect mess</a>
and I’ve had a lot of notes related discussions with friends throughout the
year.
</p>
<p>
October 15th, 2023 I decided to give
<a href="https://obsidian.md/" class="notion-text-href">Obsidian</a> a go. I
had heard about it a lot in the past and many of my friends were devoted
Obsidian users. For me, the idea of keeping notes in Markdown wasn’t a super
appealing though but the growing issues with Notion (mainly its lack of
offline functionality despite promises, slowness while using and other
hiccups), I decided to give it a go. I installed Obsidian, watched a few
Youtube videos for how to set it up, installed some plugins, created a few
templates and started making my notes in Obsidian instead of Notion.
</p>
<p>
Now it’s been two months and I’ve grown to like Obsidian. I have written about
300 notes and finally picked up a habit of daily journaling and weekly
planning & review sessions thanks to the
<a href="https://github.com/liamcain/obsidian-periodic-notes" class="notion-text-href">Periodic Notes community plugin</a>’s Daily and Weekly Notes features. It’s time for a retrospective to see what
I did, what worked and what didn’t and how I wanna continue moving forward.
</p>
<p>
During this time, I’ve used Obsidian pretty exclusively. I still have my paper
notebooks for taking notes when I’m not on computer but I’ve transferred those
notes to Obsidian regularly to make best use of the linking and search
features.
</p>
<p>
I keep track of my customizations at
<a href="https://hamatti.org/uses/obsidian" class="notion-text-href">/uses/obsidian/</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Journaling setup</h2>
<p>I have a daily note template:</p>
<pre class="language-markdown"><code class="language-markdown">---
dailyRating:
cooking:
---
## Action items
## Log
## Review</code></pre>
<p>
where I keep track of my daily feeling on a scale of 0 to 5 (with
<a href="https://github.com/pyrochlore/obsidian-tracker" class="notion-text-href">a Tracker plugin</a>
to draw graphs of how I’m doing) as well as a boolean 0/1 tracker for if I’ve
cooked food at home.
</p>
<p>
After those, I have my daily
<b class="notion-text-bold">Action items</b> (pre-scheduled things I’m
supposed to do), <b class="notion-text-bold">Log </b>(all the other actions
that pop up during the day and general notes for the day) and
<b class="notion-text-bold">Review </b>(for end-of-day notes).
</p>
<p>For a weekly note, my template looks like this:</p>
<pre class="language-markdown"><code class="language-markdown">## From last week
## Scheduled
### Monday
### Tuesday
### Wednesday
### Thursday
### Friday
### Saturday
### Sunday
## Tasks
## Review</code></pre>
<p>
Every Sunday I sit down to prepare for the next week. I take a look at the
current week and see what tasks were not done and move them to
<b class="notion-text-bold">From last week</b> section. I then go through my
calendars for the following week and note down everything that’s scheduled
like meetings and events and jot down things I know have specific days when I
should be doing them.
</p>
<p>
Finally, I have <b class="notion-text-bold">Tasks </b>section for things I
wanna do next week but don’t know yet when and
<b class="notion-text-bold">Review</b> where I can write notes when finishing
up a week.
</p>
<p>
Thanks to the plugins, these notes are automatically created from the
templates and save me a lot of time.
</p>
<h2 class="notion-heading_2 notion-color-default">Interlinked notes</h2>
<p>
One of the main features in Obsidian are links between notes with a
<code class="notion-text-code">[[topic]]</code> syntax and I’ve been using
them extensively. Originally when I heard people use Obsidian, I thought that
wouldn’t be very useful for me but quite quickly I got into a habit of adding
those links. It’s still too early to say what the impact of them will be as I
haven’t really needed to follow them while reading notes but I’m trusting that
over time, it’ll be helpful to see where I’ve linked to something interesting.
</p>
<img src="https://hamatti.org/assets/img/posts/two-months-with-obsidian/1.png" alt="A graph of my Obsidian notes with 300+ circles, many of them connected to each other. Some are in different colors. " />
<p>So far my notes seem to be quite connected.</p>
<h2 class="notion-heading_2 notion-color-default">
Lowered threshold of taking notes
</h2>
<p>
Because of the nature of Obsidian, I’ve noticed I take much more notes and
smaller notes. When I run into something interesting, I jot it down, add links
and hashtags and then let it live inside my system to be searched or
referenced later.
</p>
<p>
For example, at the beginning of the month I was pondering how coordinate
systems would work in hexagonal grids and found a short paper explaining them.
I took a screenshot of the interesting part, wrote down some notes and even
code snippets and stored them in a note.
</p>
<p>
The fastness of the app contributes to this as well. I’m more likely to jot
down a quick note because it’s instant rather than waiting for Notion to load
and creating new notes there. And I have set all new notes to land in an
<code class="notion-text-code">000 - Inbox</code> folder, I don’t have to
decide where to put notes when I start writing them.
</p>
<p>
Many Obsidian users don’t use folders at all but I like to store mine in
different categories and sub categories and moving them from Inbox to their
own places is easy and fast for example once a day or week. With
<a href="https://github.com/scambier/obsidian-omnisearch" class="notion-text-href">Omnisearch plugin</a>, I’m confident I can find what I’m looking for later down the line.
</p>
<p>
Throughout my days, I encounter interesting articles, quotes, blog posts and
videos that I store in my notes system so that when I’m writing my next
newsletter, I can find all my accumulated notes in one place nice and easy.
</p>
<h2 class="notion-heading_2 notion-color-default">Obsidian Sync</h2>
<p>
Since all the files are pure Markdown files in the filesystem, you can build
your own sync using whichever tool you want. I got through 3 days before I
purchased the official Obsidian Sync and I’ve been very happy with it. It
works so well to sync my Obsidian notes across my phone, iPad and laptop. I
can jot down a quick note on the go and then expand on it when I get home – or
make notes at home and have access to them on the road.
</p>
<h2 class="notion-heading_2 notion-color-default">
Using canvas to craft a conference talk
</h2>
<p>
I wanted to test out the features so I decided to use Obsidian’s built-in mind
map tool canvas for crafting my talk for PyCon Sweden.
</p>
<img src="https://hamatti.org/assets/img/posts/two-months-with-obsidian/2.png" alt="A zoomed out mind map with two smaller sections titled Practical and Slides at the bottom and a large section titled Talk content on the top that takes most of the space. Inside Practical and Talk content sections there are many squares connected with arrows but no text inside them. " />
<p>
Usually my approach to crafting talks is opening up Keynote, looking for
inspiration, fonts and the <i class="notion-text-italic">feel </i>of the
story. Then little by little, I craft individual slides and adjust existing
until I reach a coherent story.
</p>
<p>
This time, I started with a mind map. I have four sections: Intro, mindset,
tools and outro and I kept adding and adjusting ideas. When I was happy with
the amount and content of these pieces, I crafted them in Keynote.
</p>
<p>
It was an interesting approach and one I might use in the future as well. The
biggest benefit was that I was able to work on my talk even when I wasn’t on
my laptop with Keynote but could add ideas and references from my phone and
tablet as well.
</p>
<h2 class="notion-heading_2 notion-color-default">Downsides</h2>
<p>
Everything being Markdown is both a blessing and a curse. I do feel more
creative when I have a nice editor and Notion’s editor is way nicer than the
one in Obsidian. I can’t see myself writing blog posts in Obsidian for example
even though for other parts, it would be very handy.
</p>
<p>
Managing pictures and embeds in Markdown is annoying because what you see is
not what you edit. So if there are a lot of pictures or you need to move them
around, it gets a bit cumbersome.
</p>
<h2 class="notion-heading_2 notion-color-default">Final thoughts</h2>
<p>
I was surprised by how good did it feel like and how much my note-taking
habits changed during such a short time. I haven’t made a single new note in
Notion (I still use it to write these blog posts) and I might continue like
that. Until Notion comes up with a proper offline-first mode, it’s
unfortunately bit too rough to use for casual note-taking.
</p>
<p></p>
We’re building community for Python developers
2023-12-06T00:00:00Z
https://hamatti.org/posts/were-building-community-for-python-developers/
<p>
It’s been 8 years since I started
<a href="https://turkufrontend.fi/" class="notion-text-href">Turku ❤️ Frontend</a>
community. I had always been more of a backend developer but as I transitioned
into a non-developer role as a community manager, I wanted to stay updated
with everything happening in the fast-moving frontend developer in case the
community career didn’t pick up.
</p>
<p>
8 years later, we’ve organized over 50 events and have had 500+ developers and
nearly 30 companies involved in the community. And it’s been a lovely journey
and I’m very happy with the community we’ve managed to build.
</p>
<p>
Personally though, I’ve always found the most enjoyment in the Python
community. I’ve spoken in Python conferences and meetups across Europe and
always look forward to traveling to Python conferences and hanging out with
fellow Python developers.
</p>
<p>
I’ve wanted to do something in the local Python scene as well but the timing
hasn’t been right. The past 5 years, I’ve lived in Helsinki and Berlin and
even though I managed to keep Turku ❤️ Frontend events running thanks to our
great team in Turku, it didn’t feel right for starting a new community.
</p>
<p>
Now I’m back in Turku and for the entire year, I’ve been pondering about
starting a Python community here. A month ago I had lunch with Dan and that
lunch turned into an afternoon brainstorming session and we decided to start
the community together.
</p>
<p>
Dan came up with a great name:
<a href="https://archipylago.dev/" class="notion-text-href">archipylago</a>
which is a combination of archipelago (a very Turku thing) and Python. I like
creative names and once we hit that, we both knew it was the right one. I
bought the domain, built a website, set up
<a href="https://meetabit.com/communities/archipylago" class="notion-text-href">a Meetabit group</a>
and started spreading the news.
</p>
<p>
Our first events will start in January and we’re planning to do regular
meetups every other month and hands-on programming sprints every other. Doing
more sprints and workshops - anything where we can come together to actually
write code and learn together - was one of the discussion points we had at the
end of PyCon Sweden in November.
</p>
<p>
You can read more about our plans in our introductory blog post
<a href="https://archipylago.dev/blog/from-turku-import-archipylago/" class="notion-text-href">from turku import archipylago</a>.
</p>
<p>
I’m very excited to kickoff this new community. I hope that eventually over
time, this will lead to us bringing back PyCon Finland conferences.
</p>
<p>
If you’re interested in getting involved, I quote the aforementioned blog
post:
</p>
<blockquote class="notion-quote notion-color-default">
If you're <b class="notion-text-bold">a developer</b> interested in Python,
join us in
<a href="https://discord.gg/pAAARVc5F7" class="notion-text-href">TurkuDev discord's archipylago channels</a>
and register in
<a href="https://meetabit.com/communities/archipylago" class="notion-text-href">Meetabit</a>
to make sure you don't miss out when we start sharing information of the
upcoming events.
</blockquote>
<blockquote class="notion-quote notion-color-default">
If you work in <b class="notion-text-bold">a company</b> that uses Python, how
about you host our meetup? Get in touch either in Discord or via email
(juhamattisantala at gmail.com) and let's chat!
</blockquote>
<blockquote class="notion-quote notion-color-default">
And if you'd like to <b class="notion-text-bold">share</b> something in an
upcoming meetup or sprint, get in touch too! You don't need to be an expert
presenter or a seasoned senior developer: everyone's welcome and we can help
you with your presentation too.
</blockquote>
<p></p>
Advent of Code is just around the corner
2023-11-29T00:00:00Z
https://hamatti.org/posts/advent-of-code-is-just-around-the-corner/
<p>
For the past 8 years, developers around the globe have gathered together to
solve daily puzzles from
<a href="https://adventofcode.com/" class="notion-text-href">Advent of Code</a>. And this year is no exception. So what is Advent of Code and why should you
get excited about it? Let’s dive in.
</p>
<h2 class="notion-heading_2 notion-color-default">Save the Christmas</h2>
<p>
Advent of Code, created by
<a href="http://was.tl/" class="notion-text-href">Eric Wastl</a>, is an annual
advent calendar that runs from December 1st to December 25th. Each day, you’re
represented with a puzzle accompanied with a bit of lovely lore as the elves
need your help saving the Christmas.
</p>
<p>
As an example, last year’s overarching story was gathering star fruit that
Santa’s reindeer need:
</p>
<blockquote class="notion-quote notion-color-default">
Santa's reindeer typically eat regular reindeer food, but they need a lot of
magical energy to deliver presents on Christmas. For that, their favorite
snack is a special type of star fruit that only grows deep in the jungle. The
Elves have brought you on their annual expedition to the grove where the fruit
grows.
</blockquote>
<p>
These 50 stars are your rewards for completing 2 puzzles each day. 25 days, 2
puzzles each and a star for each successful solve.
</p>
<p>
The two puzzles of each day are related: you start with one and once you solve
that, you get another one that is often a more complex or more difficult
version of the first one. Sometimes the code you wrote in the first section is
able to handle the second one no problem with tiny adjustments - sometimes you
might have to completely rewrite the second for the second part.
</p>
<h2 class="notion-heading_2 notion-color-default">Them puzzles</h2>
<p>
Advent of Code works by providing each user a problem statement and a
(quasi-)unique input. To complete the puzzle, it asks you for an alphanumeric
answer. Let’s take a look at the first puzzle of 2020 Day 1 as an example:
</p>
<blockquote>
<p>
The jungle must be too overgrown and difficult to navigate in vehicles or
access from the air; the Elves' expedition traditionally goes on foot. As
your boats approach land, the Elves begin taking inventory of their
supplies. One important consideration is food - in particular, the number of
Calories each Elf is carrying (your puzzle input).
</p>
<p>
The Elves take turns writing down the number of Calories contained by the
various meals, snacks, rations, etc. that they've brought with them, one
item per line. Each Elf separates their own inventory from the previous
Elf's inventory (if any) by a blank line.
</p>
<p>
For example, suppose the Elves finish writing their items' Calories and end
up with the following list:
</p>
<pre class="language-bash"><code class="language-bash">1000
2000
3000
4000
5000
6000
7000
8000
9000
10000</code></pre>
<p>This list represents the Calories of the food carried by five Elves:</p>
<ul>
<li>
The first Elf is carrying food with 1000, 2000, and 3000 Calories, a total
of 6000 Calories.
</li>
<li>The second Elf is carrying one food item with 4000 Calories.</li>
<li>
The third Elf is carrying food with 5000 and 6000 Calories, a total of
11000 Calories.
</li>
<li>
The fourth Elf is carrying food with 7000, 8000, and 9000 Calories, a
total of 24000 Calories.
</li>
<li>The fifth Elf is carrying one food item with 10000 Calories.</li>
</ul>
<p>
In case the Elves get hungry and need extra snacks, they need to know which
Elf to ask: they'd like to know how many Calories are being carried by the
Elf carrying the most Calories. In the example above, this is 24000 (carried
by the fourth Elf).
</p>
<p>
Find the Elf carrying the most Calories. How many total Calories is that Elf
carrying?
</p>
</blockquote>
<p>
Your job is to calculate the result - by any means you choose. You can write
code with any programming language, do calculations with pen and paper, use
spreadsheets or something else.
</p>
<h2 class="notion-heading_2 notion-color-default">
50 stars and so many ways to participate
</h2>
<p>
I have never reached 50 stars. In 2020, I got to
<a href="https://hamatti.org/posts/advent-of-code-3-slowing-down/" class="notion-text-href">15 stars with Rust</a>. In 2021 I made a record with
<a href="https://hamatti.org/366671be681145eda265b3eb88e782f1" class="notion-text-href">45 stars with Python</a>
and in 2022 I got to
<a href="https://hamatti.org/adventofcode/2022" class="notion-text-href">38 stars with Python</a>. Globally, last year almost 290 000 people achieved one star on day one but
less than 13 000 of them reached 50.
</p>
<p>
What I’m saying here is that the worst way to “play” Advent of Code is to
stress about the stars.
</p>
<p>
There are many ways to participate in Advent of Code and finding the one that
is most fun for you is important:
</p>
<p>
Some people <b class="notion-text-bold">compete in the leaderboards. </b>The
leaderboards are based purely on speed and you need to be very experienced
puzzle-solver with usually a good setup to make it close to the top. It can be
very rewarding though.
</p>
<p>
Some people
<b class="notion-text-bold">learn new languages or technologies. </b>This has
been my approach in the past. 2020’s Rust and 2021’s Python with Jupyter
Notebooks were that. The puzzles are well defined and don’t require any
advanced language features (although you can use them) so they are great for
learning.
</p>
<p>
Some people <b class="notion-text-bold">use it to teach. </b>My
<a href="https://hamatti.org/9cd6cf3a2e42444abd3097184247e498" class="notion-text-href">last two years</a>
with Python and Jupyter Notebooks have been an exercise in technical writing
and teaching, with an added bonus of actually helping people learn.
</p>
<p>
Some people <b class="notion-text-bold">hone their base skills.</b> You solve
problem after problem, using their base programming skills to gain confidence
and strengthen those skills.
</p>
<h2 class="notion-heading_2 notion-color-default">I love the community</h2>
<p>
What I truly love about Advent of Code and what brings me back to it year
after year is how it brings together developers from different communities.
</p>
<p>
The way these puzzles are set up makes it possible for people from different
communities to come together, participate and discuss. Pretty much every
developer community I’m in has something for Advent of Code. Usually it’s a
channel in Slack or Discord or forums where people can share their solutions,
ask questions and discuss.
</p>
<p>
I’ve learned so much from other people’s solutions and it boosts my interest
in solving these because I know I get to chat about them with others who are
excited too.
</p>
<p>
Advent of Code can also be a great companion to starting technical blogging.
You’ll get 25 blog posts worth of puzzles to write about so you don’t need to
worry about coming up with ideas.
</p>
<h2 class="notion-heading_2 notion-color-default">
Tips for those who want to start
</h2>
<p>
If you got interested in Advent of Code or are a returning participant, I have
a blog post from two years back sharing
<a href="https://hamatti.org/posts/tips-for-advent-of-code/" class="notion-text-href">my tips for Advent of Code</a>.
</p>
Three travel essentials
2023-11-25T00:00:00Z
https://hamatti.org/posts/three-travel-essentials/
<p>There are three small items that are my absolute essential travel items.</p>
<h2 class="notion-heading_2 notion-color-default">100W 4-port charger</h2>
<img src="https://hamatti.org/assets/img/posts/three-travel-essentials/1.png" alt="A dark grey 100W charger with 3 USB-C ports and 1 USB-A port. A surprised Pikachu sticker in one corner. " />
<p>
The first item is a
<a href="https://eu.ugreen.com/collections/gan-chargers/products/ugreen-nexode-100w-usb-c-wall-charger" class="notion-text-href">Ugreen Nexode 100W GaN 4-port charger</a>. With just one brick, I get an adaptive max 22.5W USB-A port, an adaptive
max 22.5W USB-C port and two adaptive max 100W USB-C ports.
</p>
<p>
It’s a magical thing really. I can charge my laptop, tablet, phone,
headphones, Kindle, powerbanks and whatever I happen to bring with me without
having to remember to bring in specific chargers.
</p>
<p>
Often in hotels or trains or at other venues, you don’t have a lot of power
outlets to use so only needing one to charge everything solves a lot of
problems. And I can always help a friend out with it too.
</p>
<p>
The only downside of it is that it’s clunky and doesn’t fit into tight spaces.
My local library has power outlets in a nook in a table and I can’t charge my
laptop there.
</p>
<h2 class="notion-heading_2 notion-color-default">Cocoon Grid-It</h2>
<img src="https://hamatti.org/assets/img/posts/three-travel-essentials/2.png" alt="A Cocoon Grid-It organizer with two USB-C cables and a charger tucked in " />
<p>
The second item is
<a href="https://www.cocooninnovations.com/grid.php" class="notion-text-href">Cocoon’s Grid-It</a>. They come in many sizes and my current one is the XS which is a good
tradeoff between capacity and the space it takes in my bag. As pictured above,
it comfortably fits my charger and two cables – and I’ve been able to arrange
it with three cables as well when absolutely needed.
</p>
<p>
Having all those cables and chargers in one tidy item comes handy on trains
and venues. I don’t need to dig through my bag and find items from different
places and maybe different pouches but everything is available at once.
</p>
<p>
Its hook is especially handy when combined with the next item on the list as I
can hang it when I’m using the items, making it easy to get access to cables
and pack things back when I’m done.
</p>
<p>
I would kinda like to get one bigger one as every now and then I hope I had
bit more space in the grid.
</p>
<h2 class="notion-heading_2 notion-color-default">Heroclip</h2>
<img src="https://hamatti.org/assets/img/posts/three-travel-essentials/3.png" alt="A Heroclip carabiner with an extra hook extended " />
<p>
<a href="https://heroclip.co.uk/" class="notion-text-href">Heroclip</a> is a
carabiner with an extra hook. Mine is size medium and I constantly hope I had
bought more than just one when I purchased it. I use it all the time.
</p>
<p>
On its base form, it’s a carabiner. You can hook things into it and clip it to
something. I keep it on the handle of my backpack and often when I go around,
I clip items I want easy access but not lose them: my headphones, my water
bottle, my cap or my keys.
</p>
<p>
Once the hook is opened, its true power is revealed. I can hook my backpack on
it and hang it from the edge of a table or chair or the bathroom stall when I
don’t want the bag to touch the floor. When on a train, I gain an extra hook
to hang things like the grid-it, water bottle, headphones or snacks. Since the
hook is adjustable and hooks really well to things, any kind of handle becomes
an extra hook.
</p>
<p>
Since I bought it, I’ve also paid extra attention to buy things that have
hooks or rings so they can be hang from this.
</p>
People and Blogs newsletter
2023-11-22T00:00:00Z
https://hamatti.org/posts/people-and-blogs-newsletter/
<p>
Couple of weeks ago, I ran into a newsletter called
<a href="https://peopleandblogs.com/" class="notion-text-href">People and Blogs</a>
by
<a href="https://manuelmoreale.com/" class="notion-text-href">Manuel Moreale</a>. It’s a weekly newsletter (with RSS feed ❤️) where each week, Manuel
interviews someone who writes a blog. They discuss how they got started, what
they blog about, how they blog, what’s their view on monetizing blogs and few
other things. I find it very delightful. One thing I enjoy about about the
format is how everyone suggests blogs they enjoy as that exposes me to new
people and their writing which is a win every time.
</p>
<p>
I initially learned about it from
<a href="https://manuelmoreale.com/pb-ana-rodrigues" class="notion-text-href">Ana Rodriguez’s interview</a>. I’m a big fan of
<a href="https://ohhelloana.blog/posts/" class="notion-text-href">her writing</a>
and that worked as a great segue to learn about the newsletter.
</p>
<blockquote class="notion-quote notion-color-default">
Only at the end of 2017 did something shift inside me, thanks to
<a href="https://stage.viewsourceconf.org/london-2017/" class="notion-text-href">Jeremy Keith's talk at the ViewSource conference</a>. I discovered the IndieWeb community, and with that came the reassurance
that my "nicheless" blog was absolutely okay.
</blockquote>
<p>
As someone who’s always having internal battles with whether or not I should
focus on a single topic, I’m always reassured when I read others thinking of
the same and finding their own path and becoming happy with a blog that’s all
over the place like mine is.
</p>
<p>
Another favorite of mine was
<a href="https://manuelmoreale.com/pb-toby-shorin" class="notion-text-href">Toby Shorin’s interview</a>
where
<a href="https://subpixel.space/" class="notion-text-href">Toby</a> talked
about his process:
</p>
<blockquote class="notion-quote notion-color-default">
When I first started I got most of my ideas just from walking around downtown
New York. The visual culture here is so intense that one can get an education
by simply walking around and looking at what people are wearing, at what
advertisements are depicting. When I started off, it was enough to simply ask
questions about what I was looking at and then try to explain it from first
principles. That method required collecting hundreds of contemporary culture
references, which I could collect by simply being on the ground and noticing
lots of things.
</blockquote>
<p>
Exposing yourself to different environments, different ideas and different
medium by other people is a great way to spark creativity and I love how Toby
puts its in this quote.
</p>
<p>
From
<a href="https://manuelmoreale.com/pb-jim-nielsen" class="notion-text-href">Jim Nielsen’s interview</a>
what really got me interested in checking out
<a href="https://blog.jim-nielsen.com/" class="notion-text-href">his blog</a>
was this quote:
</p>
<blockquote class="notion-quote notion-color-default">
My blogging doesn’t feel “original” or “creative” to me. I blog because I read
other people’s blogs and I want to internalize what they said by restating it
myself. When I read, watch, or listen to something from someone else that
piques my interest, I write it down then add my proverbial two cents.
</blockquote>
<p>His views on monetizing his blog also resonated strongly with me:</p>
<blockquote class="notion-quote notion-color-default">
That said, I grew up in an era when people blogged about web stuff for free
and I benefited immensely from their work so I feel a kind of obligation to
pay it forward. Thank you blogger peeps from days of yore.
</blockquote>
<p>
I’m not against people monetizing their blogs but I feel the same way Jim does
about the value of learning from each other’s open posts and paying it forward
to the next reader.
</p>
<p>
From last week’s
<a href="https://manuelmoreale.com/pb-chris-coyer" class="notion-text-href">interview with Chris Coyier</a>
one of the quotes that resonated with me was this:
</p>
<blockquote class="notion-quote notion-color-default">
I have no idea how many times it’s been redesigned over the years! It’s my 8th
design since I’ve been properly versioning my WordPress theme, but surely a
few before that. And honestly: not enough. Redesigning your personal website
is one of life’s great pleasures.
</blockquote>
<p>
One of the best things about personal projects and your own digital home in
the Internet is that you get to do whatever you want with it.
</p>
<p>
If you’re interested in finding new blogs and hearing about the bloggers and
their thoughts behind those blogs, I recommend checking out
<a href="https://peopleandblogs.com/" class="notion-text-href">People and Blogs</a>.
</p>
<p></p>
Syntax Error #9: Distilling the minimum example
2023-11-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-9-distilling-minimum-example/
<p>
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this November issue of Syntax Error I
write about the important skill of cleaning up your problematic code from the
extra cruft, making it easier to ask questions and find issues. Read full
article at
<a href="https://www.syntaxerror.tech/syntax-error-9-distilling-the-minimum-example/">syntaxerror.tech/syntax-error-9-distilling-the-minimum-example/</a>
and either subscribe to the email or RSS feed to catch all of them.
</p>
What's in the name? The story behind my domain
2023-11-15T00:00:00Z
https://hamatti.org/posts/whats-in-the-name-the-story-behind-my-domain/
<p>
Matthias Ott runs a newsletter
<a href="https://buttondown.email/ownyourweb/archive/issue-02/" class="notion-text-href">Own Your Web and in its second issue</a>
he wrote about the domains that people have chosen for their personal blogs.
And like so often happens in the great interconnected blogosphere, other
people started writing their commentary. Michelle Barker of
<a href="http://css-irl.info/" class="notion-text-href">css-irl.info</a> wrote
about
<a href="https://css-irl.info/owning-your-web/" class="notion-text-href">her domain and the decision process that lead into that</a>. After that, fLaMEd of
<a href="http://flamedfury.com/" class="notion-text-href">flamedfury.com</a>
joined the discussion
<a href="https://flamedfury.com/posts/own-your-web-whats-in-a-name/" class="notion-text-href">writing about their blog and the domain</a>. And now I am here, talking about mine.
</p>
<p>To kick off the discussion, Matthias asked a few related questions:</p>
<blockquote class="notion-quote notion-color-default">
What is your website’s URL and why did you pick it? And are you happy with it
or would you choose a different domain name today?
</blockquote>
<p>
My website is my digital home in the Internet. There’s been quite a few before
this but the past 5-6 years I’ve been very happy to grow this one. The domain
I chose for this one is hamatti.org. I’m “Hamatti” in many places around the
web and the nickname backdates 20 years. When I was in 8th grade, I picked up
theatre studies as my optional courses and during one of our classes, a
classmate had to write my name on a chalkboard but felt Juha-Matti was too
long so he wrote down <i class="notion-text-italic">Hamatti. </i>And I took
that immediately as my own and for a good 15+ years, that’s what I was known
for amongst my friends for a long time.
</p>
<p>
I am generally happy with my domain and given I’ve been known for so long as
Hamatti around the web, it would feel silly to switch to something else and
start rebuilding that familiarity. I did buy
<a href="https://juhis.dev/" class="notion-text-href">https://juhis.dev</a>
when it become available but right now it’s just a simplified landing page
that links to this site. I could do a full redirect but I don’t want to split
the links in the web so I’d have to maintain both of them until the end.
</p>
<p>
But I have been switching my nickname from Hamatti to Juhis in most of my
circles for the past decade or so, so it would make sense at some point. Or
maybe I’ll come up with something else to do with that secondary domain in the
near future. Because are we even web developers if we don’t own dozens of
domains that are waiting for the right time?
</p>
<h2 class="notion-heading_2 notion-color-default">
Tying the domain to the “real” person or not?
</h2>
<p>
My blog itself doesn’t really have a name. It’s just a blog on my personal
website. During the
<a href="https://hamatti.org/56aa13dee4a94e73a5787c1250b48d51" class="notion-text-href">Blaugust blogging month</a>
I ended up into many discussions with people whose blogs had distinct names,
often more reflective of its contents than its author.
</p>
<p>
I like
<a href="https://css-irl.info/owning-your-web/" class="notion-text-href">Michelle’s pondering</a>
between a personal vs business sounding domain:
</p>
<blockquote class="notion-quote notion-color-default">
“CSS IRL” also sounds more like a business than a personal name, which can be
a good and bad thing. On the one hand, sounding like a business probably does
contribute to the steady and increasing flow of traffic I get, and people seem
to find it fairly memorable. On the other hand, I get a lot of email spam,
mainly from marketing companies wanting to pay me to let them publish
(probably AI generated) content on my site!
</blockquote>
<p>
<a href="https://flamedfury.com/posts/own-your-web-whats-in-a-name/" class="notion-text-href">fLaMEd writes about the same thing</a>
from a different point of view:
</p>
<blockquote class="notion-quote notion-color-default">
I, on the other hand, prefer to keep my online and work life separate. My
website has no bearing on my career, and I have no need to use it for
self-promotion. I’m content with my unconventional domain name and have no
intention of switching to a more conventional, real-name URL.
</blockquote>
<p>
I’m somewhere in the middle but heavily leaning towards the person side. While
<a href="http://hamatti.org/" class="notion-text-href">hamatti.org</a> is not
exactly my real name, it is very much personal and tied to my person and it
has had a huge impact on my career and life. And I’m not exactly hiding who is
the real person behind all of this. It comes with ups and downs but I do enjoy
when people mention in discussions that they read my blog and we can discuss
about the topics.
</p>
<h2>Additional reading</h2>
<ul>
<li>
<a href="https://blog.jim-nielsen.com/2024/origin-of-online-handles/">The Origin of Online Handles by Jim Nielsen</a>
</li>
<li>
<a href="https://bored.horse/post/online-handles/">Online handles by Thord D. Hedengren</a>
</li>
<li>
<a href="https://buttondown.email/ownyourweb/archive/issue-02/" class="notion-text-href">Own Your Web #2 by Matthias Ott</a>
</li>
<li>
<a href="https://css-irl.info/owning-your-web" class="notion-text-href">Owning your web by Michelle Barker</a>
</li>
<li>
<a href="https://flamedfury.com/posts/own-your-web-whats-in-a-name/" class="notion-text-href">Own your web: What's in the name? by fLaMEd</a>
</li>
</ul>
Done is the engine of more
2023-11-08T00:00:00Z
https://hamatti.org/posts/done-is-the-engine-of-more/
<p>
“Done is the engine of more” is the 13th item on the
<a href="https://designmanifestos.org/bre-pettis-and-kio-stark-2009-the-cult-of-done-manifesto/" class="notion-text-href">Cult of Done Manifesto</a>
by Bre Pettis and Kio Stark. And it’s the item that resonates most with me
personally.
</p>
<p>
I have written quite a lot about doing things. I’ve written about
<a href="https://hamatti.org/posts/you-should-start-a-blog-today/" class="notion-text-href">why you should start blogging</a>, about
<a href="https://hamatti.org/posts/learning-in-public/" class="notion-text-href">learning in public</a>
and about
<a href="https://hamatti.org/blog/project/" class="notion-text-href">my many projects</a>. Never
have I been able to distill what I want to say as beautifully as the title of
this blog post.
</p>
<p>
I think <i class="notion-text-italic">“Done is the engine of more” </i>is a
more powerful version of the
<i class="notion-text-italic">“perfect is the enemy of done”.</i> You can
always argue with the quality and where to draw the line but rephrasing it as
a tool that allows you to create more is a brilliant way to say it.
</p>
<p>Essentially it comes down to a couple of reasons:</p>
<p>
First, doing and finishing something
<b class="notion-text-bold">inspires more work</b>. As you go through the
process of creating something to this world, you learn about new things and
new ways and you look at things from different perspectives which often sparks
inspiration for new ideas.
</p>
<p>
Second, <b class="notion-text-bold">momentum makes it easier to start</b>.
Many of us have experienced how the start of doing anything is often the
biggest hurdle. Once you get going, continuing is easier as the momentum
carries you over. This applies to everything from washing the dishes to
writing blog posts to starting a business.
</p>
<p>
Third, finishing it can <b class="notion-text-bold">evoke feedback</b>. If you
finish something and share it with others (or just yourself), it’s an
opportunity to gain feedback and that can keep the engine going.
</p>
<p>I really like that framing.</p>
Use SQL to query your CSV files
2023-11-04T00:00:00Z
https://hamatti.org/posts/use-sql-to-query-your-csv-files/
<p>
<a href="https://csvkit.readthedocs.io/en/latest/" class="notion-text-href">csvkit</a>
is a magnificent toolbox of different tooling to manage CSV data on the
command line.
</p>
<p>
Today I’m focusing on one tool in it: csvsql. It allows you to run SQL queries
to query your CSV data, without having to set up any databases in the middle.
</p>
<h2 class="notion-heading_2 notion-color-default">The Data</h2>
<p>
Let’s start with the data. I created a CSV file from
<a href="https://en.wikipedia.org/wiki/All-time_Olympic_Games_medal_table#Combined_total_(1896%E2%80%932022)" class="notion-text-href">Wikipedia’s entry on olympic medals by country</a>
and cleaned it up a bit (removed rank, asterisks, country codes and so on) and
added headers. I saved it as
<code class="notion-text-code">olympic-medals.csv</code> and for the example’s
sake, I trimmed it to top 10 entries to give you and idea of the shape of the
data:
</p>
<p></p>
<pre class="language-plain text"><code class="language-plain text">country,gold,silver,bronze,total
United States,1174,952,833,2959
Soviet Union,473,376,355,1204
Germany,305,305,312,922
Great Britain,296,323,331,950
China,285,231,197,713
France,264,293,332,889
Italy,259,231,269,759
Sweden,212,228,239,679
Norway,209,186,173,568
Russia,194,165,186,545</code></pre>
<h2 class="notion-heading_2 notion-color-default">Running SQL queries</h2>
<p>We can now query it with SQL using csvsql.</p>
<p>Let’s start by finding the top 10 best countries:</p>
<pre class="language-plain text"><code class="language-plain text">csvsql --query \
"SELECT country, total FROM stdin ORDER BY total DESC LIMIT 10" \
< olympic-medals.csv</code></pre>
<p>and the output is</p>
<pre class="language-plain text"><code class="language-plain text">country,total
United States,2959.0
Soviet Union,1204.0
Great Britain,950.0
Germany,922.0
France,889.0
Italy,759.0
China,713.0
Sweden,679.0
Japan,573.0
Norway,568.0</code></pre>
<p>
The engine used by csvsql uses floating point numbers instead of integers so
by default you get decimal numbers as an output. If you want the final output
to have integers instead, you need to cast them:
</p>
<pre class="language-plain text"><code class="language-plain text">SELECT cast(total as integer) as total FROM stdin</code></pre>
<p>
I wouldn’t stress about it though since at least for me, the use case with
this is usually explorative to see what’s happening in the data and doing some
simple analysis rather than building it into a production pipeline.
</p>
<p>
You can find more documentation for csvsql in
<a href="https://csvkit.readthedocs.io/en/latest/tutorial/3_power_tools.html#csvsql-and-sql2csv-ultimate-power" class="notion-text-href">this tutorial</a>
or
<a href="https://csvkit.readthedocs.io/en/latest/scripts/csvsql.html" class="notion-text-href">its docs page</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">
<code class="notion-text-code">JOIN</code> two CSV files for querying
</h2>
<p>
csvsql provides the basic SQL query functionality so if you want to run
queries for multiple CSV files joined together (like with SQL’s
<code class="notion-text-code">JOIN</code> command), you need to rely on
another tool in csvkit:
<a href="https://csvkit.readthedocs.io/en/latest/scripts/csvjoin.html" class="notion-text-href">csvjoin</a>.
</p>
<p>
In this example, I’m continuing with the above medals data but add another CSV
file called <code class="notion-text-code">countries-gdp.csv</code> with
<a href="https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)" class="notion-text-href">GDP data from Wikipedia</a>. Here’s first 9 entries as an example.
</p>
<pre class="language-plain text"><code class="language-plain text">country,region,imf-forecast,imf-year,wb-estimate,wb-year,un-estimate,un-year
United States,Americas,26949643,2023,25462700,2022,23315081,2021
China,Asia,17700899,2023,17963171,2022,17734131,2021
Germany,Europe,4429838,2023,4072192,2022,4259935,2021
Japan,Asia,4230862,2023,4231141,2022,4940878,2021
India,Asia,3732224,2023,3385090,2022,3201471,2021
United Kingdom,Europe,3332059,2023,3070668,2022,3131378,2021
France,Europe,3049016,2023,2782905,2022,2957880,2021
Italy,Europe,2186082,2023,2010432,2022,2107703,2021
Brazil,Americas,2126809,2023,1920096,2022,1608981,2021</code></pre>
<p>we can combine these two CSV datasets with csvjoin:</p>
<pre class="language-plain text"><code class="language-plain text">csvjoin olympic-medals.csv countries-gdp.csv -c country</code></pre>
<p>and get a result like (a trimmed result)</p>
<pre class="language-plain text"><code class="language-plain text">country,gold,silver,bronze,total,region,imf-forecast,imf-year,wb-estimate,wb-year,un-estimate,un-year
United States,1174,952,833,2959,Americas,26949643,2023,25462700,2022,23315081,2021
Germany,305,305,312,922,Europe,4429838,2023,4072192,2022,4259935,2021
China,285,231,197,713,Asia,17700899,2023,17963171,2022,17734131,2021
France,264,293,332,889,Europe,3049016,2023,2782905,2022,2957880,2021
Italy,259,231,269,759,Europe,2186082,2023,2010432,2022,2107703,2021
Sweden,212,228,239,679,Europe,597110,2023,585939,2022,635664,2021
Norway,209,186,173,568,Europe,546768,2023,579267,2022,482175,2021
Russia,194,165,186,545,Europe,1862470,2023,2240422,2022,1778782,2021
Japan,186,178,209,573,Asia,4230862,2023,4231141,2022,4940878,2021
Hungary,183,156,182,521,Europe,203829,2023,178789,2022,181848,2021
Australia,170,180,216,566,Oceania,1687713,2023,1675419,2022,1734532,2021
Canada,148,182,221,551,Americas,2117805,2023,2139840,2022,1988336,2021
Netherlands,148,154,167,469,Europe,1092748,2023,991115,2022,1012847,2021
Finland,146,150,184,480,Europe,305689,2023,280826,2022,297302,2021</code></pre>
<p></p>
<p>
Now we could for example run a query against this new data to aggregate
average medal counts by UN regions:
</p>
<pre class="language-plain text"><code class="language-plain text">csvjoin olympic-medals.csv countries-gdp.csv -c country | \
csvsql --query \
"SELECT region, avg(gold) as gold, avg(silver) as silver,\
avg(bronze) as bronze FROM stdin GROUP BY region ORDER BY gold DESC"</code></pre>
<p>with output of</p>
<pre class="language-plain text"><code class="language-plain text">region,gold,silver,bronze
Europe,71.44736842105263,75.97368421052632,84.15789473684211
Americas,57.03703703703704,51.25925925925926,50.74074074074074
Oceania,45.4,43.4,54.0
Asia,20.945945945945947,20.16216216216216,22.864864864864863
Africa,4.5,5.214285714285714,6.0</code></pre>
<p>
Combining these different csvkit tools is straight-forward as they (mostly)
print the output to standard out and accept input from standard in.
</p>
<p>
I recommend checking out
<a href="https://csvkit.readthedocs.io/en/latest/tutorial.html" class="notion-text-href">the full csvkit tutorial</a>
to see what else it offers and how you can use it to explore your CSV data
without dumping it to a database.
</p>
Learning Rust #10: Added new feature with a HashMap
2023-11-01T00:00:00Z
https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/
<p>
<i class="notion-text-italic">In December 2020 I finally started learning Rust and built and published my
first app with Rust: </i><i class="notion-text-italic"><a href="https://hamatti.org/posts/introducing-235/" class="notion-text-href">235</a></i><i class="notion-text-italic">. </i><i class="notion-text-italic"><b class="notion-text-bold">Learning Rust </b></i><i class="notion-text-italic">is a blog series that is definitely not a tutorial but rather a place for
me to keep track of my learning and write about things I've learned along
the way.</i>
</p>
<h2 class="notion-heading_2 notion-color-default">
<b class="notion-text-bold">Learning Rust series</b>
</h2>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/" class="notion-text-href">Learning Rust #1: Pattern Matching</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-2-option-result/" class="notion-text-href">Learning Rust #2: Option & Result</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package" class="notion-text-href">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/" class="notion-text-href">Learning Rust #4: Parsing JSON with strong types</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/" class="notion-text-href">Learning Rust #5: Rustlings</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-6-ownership/" class="notion-text-href">Learning Rust #6: Understanding ownership in Rust</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community" class="notion-text-href">Learning Rust #7: Learn from the community</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/" class="notion-text-href">Learning Rust #8: What's next?</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings" class="notion-text-href">Learning Rust #9: A talk about rustlings</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
Learning Rust #10: Added new feature with a HashMap (you are here)
</li>
</ul>
<p>
For
<a href="https://github.com/Hamatti/nhl-235/issues/43" class="notion-text-href">quite a bit</a>, I’ve been thinking about wanting to show the assists of specific players in
the output. Yle’s teletext page does this for Finnish players by adding them
in parenthesis below the line of a goal scored where those players gained
assists.
</p>
<p>
That was my starting point and in the GitHub linked issue and in my early
notes, that’s what I was planning to do. The way the print logic is built
right now doesn’t make it super easy though since it operates on a
line-by-goals basis.
</p>
<p>
This fall, I started pondering this again and I figured it’s not as important
to know which goals they assisted but rather, how many goals and assists they
gained in total.
</p>
<p>
Since version 1.2.0, users of 235 have been able to write a list of player
names they want to follow. This can be achieved by creating a list of last
names in <code class="notion-text-code">$HOME/.235.config</code> file and used
with
<code class="notion-text-code"><a href="https://hamatti.github.io/nhl-235/#docs-highlight" class="notion-text-href">--highlight</a></code><a href="https://hamatti.github.io/nhl-235/#docs-highlight" class="notion-text-href">
flag</a>. I decided to use this same list for which players to show their full stats.
</p>
<p>
In the spirit of Learning Rust series, I’ll take you to a journey of how I
crafted this feature and how I went through multiple rounds of refactoring to
clean up the code.
</p>
<h3 class="notion-heading_3 notion-color-default">
Refactoring to Options struct
</h3>
<p>
I have used boolean variables previously to keep track of command line
options, passing them through in various functions. With the amount of this
value growing to 3 now, I figured it’s time to refactor them to use a single
<code class="notion-text-code">options</code> variable to keep hold of them.
</p>
<p>I started by defining a struct for these three options:</p>
<pre class="language-rust"><code class="language-rust">struct Options {
use_colors: bool,
show_highlights: bool,
show_stats: bool,
}</code></pre>
<p>and then creating it in the main function:</p>
<pre class="language-rust"><code class="language-rust">let options: Options = Options {
use_colors: !args.nocolors,
show_stats: args.stats,
show_highlights: args.highlight,
};</code></pre>
<p>This cleaned up the code down the line significantly.</p>
<h3 class="notion-heading_3 notion-color-default">
Crafting the stats message with a HashMap
</h3>
<p>
When it comes time to (potentially) print the stats message, I use a
`std::collections::HashMap` to collect points for the desired players.
</p>
<p>
Coming back to Rust after a full year, I struggled quite a bit in the
beginning and my first version was very verbose and overly step-by-step:
</p>
<pre class="language-rust"><code class="language-rust">fn print_stats(home_scores: &Vec<&Goal>, away_scores: &Vec<&Goal>, highlights: &[String]) {
let mut stats: HashMap<String, Stat> = HashMap::new();
home_scores.iter().for_each(|&goal| {
if highlights.contains(&goal.scorer) {
let current_score = match stats.get(&goal.scorer) {
Some(stat) => stat.goals,
None => 0,
};
let current_assists = match stats.get(&goal.scorer) {
Some(stat) => stat.assists,
None => 0,
};
let new_stat: Stat = Stat {
goals: current_score + 1,
assists: current_assists,
};
stats.insert(String::from(&goal.scorer), new_stat);
}
goal.assists.iter().for_each(|assist| {
if highlights.contains(&assist) {
let current_goals = match stats.get(assist) {
Some(stat) => stat.goals,
None => 0,
};
let current_assists = match stats.get(assist) {
Some(stat) => stat.assists,
None => 0,
};
let new_stat: Stat = Stat {
goals: current_goals,
assists: current_assists + 1,
};
stats.insert(String::from(assist), new_stat);
}
})
});
away_scores.iter().for_each(|&goal| {
if highlights.contains(&goal.scorer) {
let current_goals = match stats.get(&goal.scorer) {
Some(stat) => stat.goals,
None => 0,
};
let current_assists = match stats.get(&goal.scorer) {
Some(stat) => stat.assists,
None => 0,
};
let new_stat: Stat = Stat {
goals: current_goals + 1,
assists: current_assists,
};
stats.insert(String::from(&goal.scorer), new_stat);
}
goal.assists.iter().for_each(|assist| {
if highlights.contains(&assist) {
let current_goals = match stats.get(assist) {
Some(stat) => stat.goals,
None => 0,
};
let current_assists = match stats.get(assist) {
Some(stat) => stat.assists,
None => 0,
};
let new_stat: Stat = Stat {
goals: current_goals,
assists: current_assists + 1,
};
stats.insert(String::from(assist), new_stat);
}
})
});
if stats.is_empty() {
return;
}
let mut message = String::from("(");
for (name, stats) in stats.iter() {
message.push_str(name);
message.push_str(" ");
message.push_str(&stats.goals.to_string());
message.push_str("+");
message.push_str(&stats.assists.to_string());
message.push_str(", ");
}
let len = message.len();
message = String::from(&message[..len - 2]);
message.push_str(")");
yellow_ln!("{}", message);
println!();
}</code></pre>
<p>
There’s a lot of repetition and a lot of extra work done but it helped me find
a way to make something work.
</p>
<p>
For each goal in home and away scores, I extracted the current goal and assist
counts for a player, added one to the correct one and inserted it back to the
HashMap.
</p>
<h3 class="notion-heading_3 notion-color-default">Let’s start refactoring</h3>
<p>
First thing I did to start refactoring, was to extract the counting of stats
into its own function and combine the goals and assists accessing into one get
call and pattern match.
</p>
<pre class="language-rust"><code class="language-rust">fn count_stats(goal: &Goal, stats: &mut HashMap<String, Stat>, highlights: &[String]) {
if highlights.contains(&goal.scorer) {
let new_stat = match stats.get(&goal.scorer) {
Some(stat) => Stat {
goals: stat.goals + 1,
assists: stat.assists,
},
None => Stat {
goals: 1,
assists: 0,
},
};
stats.insert(String::from(&goal.scorer), new_stat);
}
goal.assists.iter().for_each(|assist| {
if highlights.contains(&assist) {
let new_stat = match stats.get(&goal.scorer) {
Some(stat) => Stat {
goals: stat.goals,
assists: stat.assists + 1,
},
None => Stat {
goals: 0,
assists: 1,
},
};
stats.insert(String::from(assist), new_stat);
}
})
}
fn print_stats(
home_scores: &Vec<&Goal>,
away_scores: &Vec<&Goal>,
highlights: &[String],
show_highlights: bool,
) {
let mut stats: HashMap<String, Stat> = HashMap::new();
home_scores.iter().for_each(|&goal| {
count_stats(&goal, &mut stats, &highlights);
});
away_scores.iter().for_each(|&goal| {
count_stats(&goal, &mut stats, &highlights);
});
// Rest same as above
}
</code></pre>
<p>
It still felt a bit too manual and I figured there must be a better way.
Luckily, I’m part of a great community of Rust developers in Finland through
<a href="https://koodiklinikka.fi/" class="notion-text-href">Koodiklinikka community</a>
and asked for help in our Slack.
</p>
<p>
I was pointed out to the trio of methods
<code class="notion-text-code"><a href="https://doc.rust-lang.org/stable/std/collections/hash_map/enum.Entry.html" class="notion-text-href">.entry()</a></code>,
<code class="notion-text-code"><a href="https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.and_modify" class="notion-text-href">.and_modify()</a></code>
and
<code class="notion-text-code"><a href="https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert" class="notion-text-href">.or_insert()</a></code>:
</p>
<pre class="language-rust"><code class="language-rust">fn count_stats(goal: &Goal, stats: &mut HashMap<String, Stat>, highlights: &[String]) {
if highlights.contains(&goal.scorer) {
stats
.entry(String::from(&goal.scorer))
.and_modify(|stat| stat.goals += 1)
.or_insert(Stat {
goals: 1,
assists: 0,
});
}
goal.assists.iter().for_each(|assist| {
if highlights.contains(&assist) {
stats
.entry(String::from(assist))
.and_modify(|stat| stat.assists += 1)
.or_insert(Stat {
goals: 0,
assists: 1,
});
}
})
}</code></pre>
<p>
These allow modification of individual fields within the value of a HashMap
with a default insert if the key isn’t found. I love the interface: it’s easy
to read and very compact.
</p>
<p>
After a good night sleep, I also realized it’s bit silly to pass both home and
away goals as separate vectors to this function when I already had a list of
all goals so I refactored the print function to accept one list of goals and
iterate over that:
</p>
<pre class="language-rust"><code class="language-rust">fn print_stats(goals: &Vec<Goal>, highlights: &[String], options: &Options) {
let mut stats: HashMap<String, Stat> = HashMap::new();
goals.iter().for_each(|goal| {
count_stats(&goal, &mut stats, &highlights);
});
if stats.is_empty() {
return;
}
let mut stats_messages: Vec<String> = Vec::new();
for (name, stats) in stats.iter() {
let sub_message = format!(
"{} {}+{}",
name,
&stats.goals.to_string(),
&stats.assists.to_string()
);
stats_messages.push(sub_message);
}
let message: String = format!("({})", stats_messages.join(", "));
if options.show_highlights {
yellow_ln!("{}", message);
} else if options.use_colors {
white_ln!("{}", message);
} else {
println!("{}", message);
}
println!();
}</code></pre>
<p>
I also replaced my manual message creation with two changes: I use
<code class="notion-text-code"><a href="https://doc.rust-lang.org/std/macro.format.html" class="notion-text-href">format!</a></code>
to craft the individual parts for each player inside the for loop and then use
<code class="notion-text-code"><a href="https://doc.rust-lang.org/std/vec/struct.Vec.html#method.join" class="notion-text-href">join()</a></code>
to combine all of them. And since I’m passing the
<code class="notion-text-code">options</code> struct here, I have a cleaner
way to manage the right colors with printing.
</p>
<p>
With there now only being one
<code class="notion-text-code">goals</code> vector to play with, I can move
the HashMap creation into
<code class="notion-text-code">count_stats</code> function and return it from
there, instead of creating it one level higher and modifying it inside another
function.
</p>
<pre class="language-rust"><code class="language-rust">fn count_stats(goals: &Vec<Goal>, highlights: &[String]) -> HashMap<String, Stat> {
let mut stats: HashMap<String, Stat> = HashMap::new();
goals.iter().for_each(|goal| {
if highlights.contains(&goal.scorer) {
stats
.entry(String::from(&goal.scorer))
.and_modify(|stat| stat.goals += 1)
.or_insert(Stat {
goals: 1,
assists: 0,
});
}
goal.assists.iter().for_each(|assist| {
if highlights.contains(&assist) {
stats
.entry(String::from(assist))
.and_modify(|stat| stat.assists += 1)
.or_insert(Stat {
goals: 0,
assists: 1,
});
}
})
});
return stats;
}
// In print_stats:
let stats: HashMap<String, Stat> = count_stats(&goals, &highlights);</code></pre>
<p>
Finally, I extracted the crafting of the stats message to a separate function
from the print stats function so it can be tested.
</p>
<pre class="language-rust"><code class="language-rust">fn craft_stats_message(goals: &Vec<Goal>, highlights: &[String]) -> Option<String> {
let stats: HashMap<String, Stat> = count_stats(&goals, &highlights);
if stats.is_empty() {
return None;
}
let mut stats_messages: Vec<String> = Vec::new();
for (name, stats) in stats.iter() {
let sub_message = format!(
"{} {}+{}",
name,
&stats.goals.to_string(),
&stats.assists.to_string()
);
stats_messages.push(sub_message);
}
return Some(format!("({})", stats_messages.join(", ")));
}</code></pre>
<p>And then I wrote tests to make sure this works properly.</p>
<pre class="language-rust"><code class="language-rust">#[test]
fn it_crafts_no_message_if_no_highlighted_players_gain_stats() {
let highlights: Vec<String> = vec![String::from("Crosby")];
let goal: Goal = Goal {
scorer: String::from("Malkin"),
assists: vec![String::from("Letang"), String::from("Karlsson")],
minute: 21,
special: false,
team: String::from("Pittsburg"),
};
let expected: Option<String> = None;
let actual: Option<String> = craft_stats_message(&vec![goal], &highlights);
assert_eq!(actual, expected);
}
#[test]
fn it_crafts_good_message_if_player_scored() {
let highlights: Vec<String> = vec![String::from("Crosby")];
let goal: Goal = Goal {
scorer: String::from("Crosby"),
assists: vec![String::from("Letang"), String::from("Karlsson")],
minute: 21,
special: false,
team: String::from("Pittsburg"),
};
let expected: Option<String> = Some(String::from("(Crosby 1+0)"));
let actual: Option<String> = craft_stats_message(&vec![goal], &highlights);
assert_eq!(actual, expected);
}
// + more</code></pre>
<h2 class="notion-heading_2 notion-color-default">
Release Notes Driven Development
</h2>
<p>
If you look at
<a href="https://github.com/Hamatti/nhl-235/pull/44/commits" class="notion-text-href">the commit log of this changeset</a>, you notice I started with the release notes, then wrote documentation and
only after that, started writing the implementation.
</p>
<p>
I really like the approach of starting from the usage and documentation side
of things when implementing new features. It shifts the focus into what I
believe to be most important parts of the software development: how it will be
used and how it looks like to the end user.
</p>
<p>
Instead of letting the implementation take the lead and then maybe writing
some docs as an afterthought, I want to put my best energy towards that.
</p>
<p>
So I ended up writing
<a href="https://hamatti.github.io/nhl-235/release-notes/1.3.0" class="notion-text-href">the release notes for this version 1.3.0</a>
and thought about how this feature will look like for the end user. Then I
wrote
<a href="https://hamatti.github.io/nhl-235#docs-stats" class="notion-text-href">the documentation</a>
on how to actually use it.
</p>
<p>
This helped me solidify a good user interface, go through some questions that
came up with myself about certain corner cases and design decisions. That in
turn helped me focus on the implementation when I already knew how I wanted it
to work out and had in this case all the questions answered already.
</p>
<p>
The best part is that after I’m done with implementation, testing and multiple
rounds of iterations, I already have everything else ready. I find myself not
being at my best at the end of the coding part, often eager to move to the
next thing or just get it out the door which leads to documentation being
sometimes overlooked or not as polished as it should be.
</p>
Showing most popular posts with Netlify Analytics
2023-10-28T00:00:00Z
https://hamatti.org/posts/showing-most-popular-posts-with-netlify-analytics/
<p>
If you’re visiting this blog on a window size large enough to see the sidebar
on the right side of the this page, you can see there’s a new section:
<b class="notion-text-bold">Popular posts (30 days). </b>If you’re on mobile,
you have to wait for a while until I figure out a good way to display them on
my website.
</p>
<p>
Inspiration for this project came from Jim Nielsen’s blog post
<a href="https://blog.jim-nielsen.com/2020/using-netlify-analytics-to-build-list-of-popular-posts/" class="notion-text-href">Using Netlify Analytics to Build a List of Popular Posts</a>
that I discovered last week. It inspired me to build a similar feature and
experiment a bit on how I display my blog post listings.
</p>
<p>
There are three parts to the current implementation: fetching the most popular
posts, storing them on a JSON file and displaying them.
</p>
<h2 class="notion-heading_2 notion-color-default">
Fetching posts from Netlify Analytics API
</h2>
<h3 class="notion-heading_3 notion-color-default">
Site ID and Personal token
</h3>
<p>
To get started with the Netlify Analytics API, you need two things: your site
ID and a personal token.
</p>
<p>
To find your site ID, you need to go to your Netlify dashboard, open Site
configuration and inside Site information, you can see your site ID. Store
that with your environment values.
</p>
<p>
To generate your personal token, follow the instructions from
<a href="https://docs.netlify.com/api/get-started/#authentication" class="notion-text-href">Netlify’s documentation</a>. Once you have it, store it with your environment values.
</p>
<p>
This is a good moment to also make sure you
<a href="https://hamatti.org/posts/document-your-secrets/" class="notion-text-href">document to your project how these are found and generated</a>. Down the line when you set up your environment on a new computer, you’ll
thank yourself for documenting them!
</p>
<h3 class="notion-heading_3 notion-color-default">Script to fetch the data</h3>
<p>
The analytics API is not documented in Netlify’s docs but thanks to Jim’s blog
post and poking around with developer tools, I found the format:
</p>
<pre class="language-rust"><code class="language-rust">https://app.netlify.com/access-control/analytics-api/v2/${process.env.NETLIFY_SITE_ID}/ranking/pages?from=${from}&to=${to}&timezone=+0300&limit=30</code></pre>
<p>
It requires 3 variables: the site ID that I access through environment values
and <code class="notion-text-code">from</code> and
<code class="notion-text-code">to</code> timestamps as milliseconds for the
range of data.
</p>
<pre class="language-javascript"><code class="language-javascript">// 30 day window
let to = Date.now();
let from = to - 2592000000;</code></pre>
<p>
To authenticate your requests, you need to add this to your request headers:
</p>
<pre class="language-javascript"><code class="language-javascript">let headers = {
Authorization: `Bearer ${process.env.NETLIFY_TOKEN}`,
}</code></pre>
<p>
Now you’re ready to make a call and the response contains a list of objects
(with some dummy data from mine, not actual values) like this:
</p>
<pre class="language-json"><code class="language-json">{
data: [
{ count: 55555, resource: '/' },
{ count: 44444, resource: '/tabletop/potluck/' },
{
count: 33333,
resource: '/posts/projects-im-proud-of-boost-turku-and-startup-journey/'
},
{
count: 22222,
resource: '/posts/website-rewrite-and-switching-to-notion-as-cms/'
}
]
}</code></pre>
<p>
To find my blog posts, I filter out anything that doesn’t start
<code class="notion-text-code">/posts/</code> and then take the top 5.
</p>
<h2 class="notion-heading_2 notion-color-default">Store in JSON</h2>
<p>
After this, I find other information about these posts from my files’
frontmatter and store them in a JSON file
<code class="notion-text-code">_data/most_popular.json</code>
</p>
<p>
I use
<a href="https://www.npmjs.com/package/front-matter" class="notion-text-href">front-matter package</a>
to parse the post’s frontmatter based on its slug and then store the title,
description, date and URL to the JSON.
</p>
<h2 class="notion-heading_2 notion-color-default">Display the results</h2>
<p>This data is then available to my templates so I can iterate over it:</p>
<pre class="language-html"><code class="language-html"><h2>Popular posts (30 days)</h2>
<ul>
{% for post in most_popular %}
<li>
<a href="{{post.url}}">{{ post.title}}</a>
</li>
{% endfor %}
</ul></code></pre>
<p>
Currently my display format is simple but you could show the date or the
number of views (like
<a href="https://blog.jim-nielsen.com/" class="notion-text-href">Jim does</a>)
or whatever you wish.
</p>
<p>
Currently mine needs to be run manually to update but I basically make small
adjustments, improvements and content updates to my site every day anyway so
it will be up to date mostly all the time. And my blog’s favourite posts tend
to be quite stable so there’s often no need to have more granular updates than
once a day for now.
</p>
<p>
Other people have done similar things with different analytics services. Robb
Knight
<a href="https://rknight.me/popular-pages-with-eleventy-and-fathom-analytics/">uses Fathom analytics and shows his most popular posts in his site</a>
and Cory Dransfeldt has written about
<a href="https://coryd.dev/posts/2023/popular-posts-widget-using-eleventy-plausible/">how to do it with Plausible analytics</a>.
</p>
Priority order when you start blogging
2023-10-25T00:00:00Z
https://hamatti.org/posts/priority-order-when-you-start-blogging/
<p>
Let’s say you have decided to start a blog. Congratulations, it’s a good
decision. I think
<a href="https://hamatti.org/posts/you-should-start-a-blog-today/" class="notion-text-href">everyone should at least consider having a blog</a>. Now then, what should you do first?
</p>
<h2 class="notion-heading_2 notion-color-default">
Priority #1: Write the first blog post
</h2>
<p>
The first priority for any blog should be to write the first post. No matter
how you do it: in an existing blogging platform, locally with Markdown on your
computer, with a quill, ink and paper – just do it. A blog with zero posts is
not really a blog, it’s just a sad case of lost potential.
</p>
<p>
I’ve written before about how
<a href="https://hamatti.org/posts/tools-dont-matter-until-they-do/" class="notion-text-href">the decision of what tools you pick in the beginning is not that
important</a>. Don’t stress it, just pick one. At worst, you need to do a bit of manual
work when migrating to a different system once you find what you actually
need.
</p>
<p>
My advice for the first post is to not write about how you decided to start a
blog and what tools you use to publish it and what you’ll eventually write.
There are too many blogs in the world where that ends up being the only post.
Write something of substance.
</p>
<p>
If you want to start a tech blog, write a post about something you learned
recently or something you had to figure out by googling, reading Stack
Overflow and documentation and some GitHub issues. Collect that knowledge you
acquired into a blog post. If you want to start a cooking blog, cook something
and write about that. Write some story before the recipe even, don’t let the
haters stop those beautiful stories. If you’re traveling and want to share
stories from across the globe, write about your latest trip.
</p>
<p>
Go straight in. You don’t need to write a blog post where you first tell
people what you’re planning to write in the future.
</p>
<h2 class="notion-heading_2 notion-color-default">
Priority #2: Make sure you have RSS feed available
</h2>
<p>
I think the most important thing to do once you have written your first post
is to make sure the platform or tooling you use builds
<a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed/" class="notion-text-href">a RSS feed</a>.
</p>
<p>
I may sound like a RSS salesman but hear me out. Even if RSS isn’t the most
mainstream thing right now, a lot of readers especially in tech use them.
</p>
<p>
And it’s especially valuable to at the beginning: I may run into your blog,
see your first blog post and get interested. But since there’s no other
content, I’m heading out after I’ve read it and I might not run into links to
your blog again for years. For new blogs, I’m not as motivated to manually
bookmark them and go check them out all the time because so many new blogs are
abandoned after the first post. So I lose out on a potentially great and
interesting blog and you lose a reader who’s interested in what you have to
say.
</p>
<p>
This happened to me recently. There was a blogger I knew in person for a few
years and every now and then I saw them post on social media and I’d read
their posts. But then our paths went different ways and 5-6 years later I
accidentally ran into their blog again to find that they had been actively
writing interesting pieces all this time. But since they didn’t have RSS feed
available at the start, I didn’t know. When I found it now, they had added RSS
feed and I became an active happy reader again.
</p>
<h2 class="notion-heading_2 notion-color-default">Priority #3: Write more</h2>
<p>
Go and write more! Pour your heart and your soul onto the paper and the pages
in the web. Share, document, inspire, entertain. Find a pace of publishing
that works for you: maybe it’s daily, weekly, monthly or at completely random
intervals. There are no rules.
</p>
<p>
Write what you find worth writing and sharing and let your writing find their
audience rather than the other way around.
</p>
<p>
Last week I listened to a webinar by Finnish literature podcaster Marko Suomi
of
<a href="https://takakansi.fi/" class="notion-text-href">Takakansi</a>
podcast. What really resonated with me in that discussion was how for him,
running a podcast deepened his interest towards the topic and made him learn
more about it because of the podcast.
</p>
<p>
I find the same applies to blogging. Because I blog, I look at the world
through different scopes and when I’m researching a topic and writing about
it, I learn more which leads to my interest to the topic deepening.
</p>
<p>
And I find myself searching my blog constantly when I need something because I
remember I wrote about it in the past. It’s a publicly available personal
documentation system in addition to being a blog that readers enjoy to read.
</p>
Great details on conference badges
2023-10-18T00:00:00Z
https://hamatti.org/posts/great-details-on-conference-badges/
<p>
Over the past decade, I’ve been to quite a few events that had different
styles of event badges. The simplest ones for smaller events only had my name
(some handwritten by myself) but there are many that have really cool details
and information on the badges.
</p>
<p>
What’s been your favorite? You can comment on the post by replying to the
Mastodon post at the end of this post.
</p>
<h2 class="notion-heading_2 notion-color-default">
QR Code with contact information, Strata Conf 2014
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/1.png" alt="A backside of conference badge with a QR code and text “This QR code contains information you provided when you registered for this event, including: name, email, phone, city, twitter, affilaition and position. Scan with a smart phone to share your digital business card. " />
<p>
In 2014, I visited Strata Conf in Santa Clara, US and on the backside of the
badge, there was a QR code (I replaced mine above with a dummy one). When
scanned, it would show plain text name, email address, title, etc, with the
idea being you could let other event participants scan your QR code and get
your information.
</p>
<p>
The data being in plain text form instead of opening a URL to some service is
also smart since it doesn’t rely on Internet connection and the data is still
there because it’s all encoded in that QR code itself.
</p>
<h2 class="notion-heading_2 notion-color-default">
Wifi information, SEFI 2013
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/2.png" alt="A conference badge backside with my name and wifi information for the event venue " />
<p>
In 2013 SEFI conference in Leuven, Belgium, the badge contained information
for the available wifi.
</p>
<h2 class="notion-heading_2 notion-color-default">
Organizer phone numbers, Brainfactory/Legal Design Summit 2017
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/3.png" alt="A backside of a hackathon badge with schedule overview for Friday through Sunday, sponsor logos and contact details with phone numbers censored with black bars. " />
<p>
Legal Design Summit’s Brainfactory was a legal design hackathon organized in
Helsinki 2017 and I was coaching in the event. Directly in the badge, there
were contact information of the event organizers and the addresses of the
venues. No need to dig through emails and trying to find who to call if
something happens and there’s need to get in touch. Probably not a feasible
thing for large conferences but for this kind of smaller hackathon, I think it
it brilliant addition.
</p>
<h2 class="notion-heading_2 notion-color-default">
URL to Q&A, Smartly.io DevTalks
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/4.png" alt="A meetup badge for Smartly DevTalks with my name and a URL to Slido for Q&A " />
<p>
Smartly organized a series of DevTalks meetups in their Helsinki office around
2019 and a small nice detail on the badges is an inclusion of the Slido Q&A
link. Often in events, platforms like Slido are used for Q&A but it’s easy to
forget the URL if it’s only mentioned once at the start of the event day or if
there are multiple tracks all requiring their own.
</p>
<p>
That’s why I think this is such a good addition. When I do talks that use
Slido, I add the link to each slide so when questions pop to people’s minds,
they can find that information right away and are more likely to ask their
question.
</p>
<h2 class="notion-heading_2 notion-color-default">
Venue map, Slush 2019 & SHIFT Business Festival 2017 (+ many others)
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/5.png" alt="Two conference badge backsides with maps and their legends. " />
<p>
After the name of the event and the person, one of the most common addition to
the backside of the badge were conference venue maps. In the example above,
Slush 2019 and SHIFT Business Festival 2017 had clear and easy to read maps
with the basic required information.
</p>
<p>
For larger events with multiple points of interest and tracks, these maps can
save so much confusion and headache from participants. A small extra perk is
that it makes it much easier to agree on a meeting spot with a friend during
the event when you can point to a number or a letter on the map.
</p>
<h2 class="notion-heading_2 notion-color-default">
Event schedule, PyCon CZ 2019 (+ many others)
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/6.png" alt="A two-page inside of a conference badge with schedule of Friday with two tracks. " />
<p>
Another very common addition is the schedule of the event. Above picture is
from PyCon CZ 2019 (you can find my talk there right before lunch on the upper
section).
</p>
<p>
Not having to rely on phone battery and Internet to check information about
talks, which rooms they are in and when they start is a win in my book. PyCon
CZ has these multi-page badges (that open up in a direction that you can read
it while it hangs from your neck!) with schedule information and other venue
and event info.
</p>
<h2 class="notion-heading_2 notion-color-default">
A coaster, React Finland 2022
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/7.png" alt="A round, bright orange conference badge for React Finland 2022 with the event logo, my name, company name, Twitter handle and role “Organizer”. " />
<p>
The round badges of React Finland 2022 did double duty as a drinks coaster
after the event. While not recommended to be used for that during the event, I
love when you can give a new life to a badge as something else after the
event.
</p>
<p>
I’m too attached to the memories to actually use it as one though as I like to
collect these badges into a box of memories.
</p>
<h2 class="notion-heading_2 notion-color-default">
Plantable badge, Django Day Copenhagen 2022
</h2>
<img src="https://hamatti.org/assets/img/posts/great-details-on-conference-badges/8.png" alt="A white, organic looking badge with a sticker with Django Day Copenhagen logo and my name on it " />
<p>
Django Day Copenhagen’s badge is the most unique I’ve gotten. During the
conference day, it acts as a badge with the name on it but after the event
when you get home, you could plant it into a pot with some soil and give it
some love to grow it into a plant.
</p>
<p></p>
Syntax Error #8: What we can learn from hardware debugging?
2023-10-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-8-hardware-debugging/
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this October issue of Syntax Error I took
the ducks to see hardware debugging and we wrote down key learnings for software
developers. Read full article at
<a href="https://www.syntaxerror.tech/syntax-error-8-what-we-can-learn-from-hardware-debugging/">syntaxerror.tech/syntax-error-8-what-we-can-learn-from-hardware-debugging/</a>
and either subscribe to the email or RSS feed to catch all of them.
I got to show you this
2023-10-14T00:00:00Z
https://hamatti.org/posts/i-got-to-show-you-this/
<p>
It was 3:30 in the morning. I was sitting on the edge of my bed with my laptop
on my night stand and I was cheering for a red marble to defeat a blue marble
in various competitions. Wait, what? Let me introduce you to a very peculiar
corner of the Internet: Youtube racing channels. But not the kind of racing
you might be thinking.
</p>
<h2 class="notion-heading_2 notion-color-default">Jelle’s Marble Runs</h2>
<p>
For me it all started with
<a href="https://www.youtube.com/@JellesMarbleRuns" class="notion-text-href">Jelle’s Marble Runs</a>. I don’t know how or why the Youtube recommendation engine decided that this
was my future addiction but it did. Jelle’s Marble Runs is a Youtube channel
with <b class="notion-text-bold">1.4 million </b>subscribers where they build
various different downhill tracks, give names and team names to various
differently colored marbles and race them against each other. Combined with
high production quality and enthusiastic commentary, it’s easy to forget that
those marbles don’t have any agency.
</p>
<p>If you’re not convinced, give this video a watch:</p>
<div class="embed-container" data-url="https://www.youtube.com/watch?v=UdYshiTGla0">
<button class="youtube-thumbnail" aria-label="Play Youtube video undefined">
<img src="https://hamatti.org/assets/img/posts/i-got-to-show-you-this/UdYshiTGla0.jpg" alt="Youtube thumbnail for undefined" />
<img src="https://hamatti.org/assets/img/play-button.png" class="play-button" alt="Play" />
</button>
<div class="consent-form hide">
<p class="consent-header">
<strong>Consent for 3rd party cookies</strong><button class="close-consent" aria-label="Close consent form">x</button>
</p>
<p>
<a href="https://policies.google.com/technologies/cookies" target="_blank">Youtube sets cookies</a>
when you watch embedded videos. By clicking <strong>Accept</strong>, you
consent to these cookies being set. Alternatively, you can click
<strong>Watch on Youtube</strong> to watch it there or
<strong>Decline</strong> to prevent video from being shown.
</p>
<div class="consent-nav">
<button class="consent-confirm">Accept</button>
<button class="close-consent">Decline</button>
<a href="https://www.youtube.com/watch?v=UdYshiTGla0" target="_blank">Watch on Youtube</a>
</div>
</div>
</div>
<h2 class="notion-heading_2 notion-color-default">
<b class="notion-text-bold">Diecast Racing League</b>
</h2>
<p>
But it’s not just marbles that do racing. Diecast Racing League is pretty much
the same concept but with miniature toy cars like Hot Wheels and other
manufacturers.
</p>
<p>
I don’t know about you but watching the DRC Rally Season 1 for the first time,
I found myself cheering audibly (luckily, alone at my home) for “Superman”
Steven King who became an instant favorite of mine from the first race
onwards.
</p>
<p>
Gravity + downhill + toys + high production values and it can get incredibly
exciting.
</p>
<div class="embed-container" data-url="https://www.youtube.com/watch?v=o2tsSCAqwW8">
<button class="youtube-thumbnail" aria-label="Play Youtube video undefined">
<img src="https://hamatti.org/assets/img/posts/i-got-to-show-you-this/o2tsSCAqwW8.jpg" alt="Youtube thumbnail for undefined" />
<img src="https://hamatti.org/assets/img/play-button.png" class="play-button" alt="Play" />
</button>
<div class="consent-form hide">
<p class="consent-header">
<strong>Consent for 3rd party cookies</strong><button class="close-consent" aria-label="Close consent form">x</button>
</p>
<p>
<a href="https://policies.google.com/technologies/cookies" target="_blank">Youtube sets cookies</a>
when you watch embedded videos. By clicking <strong>Accept</strong>, you
consent to these cookies being set. Alternatively, you can click
<strong>Watch on Youtube</strong> to watch it there or
<strong>Decline</strong> to prevent video from being shown.
</p>
<div class="consent-nav">
<button class="consent-confirm">Accept</button>
<button class="close-consent">Decline</button>
<a href="https://www.youtube.com/watch?v=o2tsSCAqwW8" target="_blank">Watch on Youtube</a>
</div>
</div>
</div>
<h2 class="notion-heading_2 notion-color-default">League of Pigs</h2>
<p>And then someone took their pigs and built a race track for them.</p>
<p>
No, I’m serious. League of Pigs might just be the wildest animal racing league
in the world.
</p>
<div class="embed-container" data-url="https://www.youtube.com/watch?v=8jLNNMFur5A">
<button class="youtube-thumbnail" aria-label="Play Youtube video undefined">
<img src="https://hamatti.org/assets/img/posts/i-got-to-show-you-this/8jLNNMFur5A.jpg" alt="Youtube thumbnail for undefined" />
<img src="https://hamatti.org/assets/img/play-button.png" class="play-button" alt="Play" />
</button>
<div class="consent-form hide">
<p class="consent-header">
<strong>Consent for 3rd party cookies</strong><button class="close-consent" aria-label="Close consent form">x</button>
</p>
<p>
<a href="https://policies.google.com/technologies/cookies" target="_blank">Youtube sets cookies</a>
when you watch embedded videos. By clicking <strong>Accept</strong>, you
consent to these cookies being set. Alternatively, you can click
<strong>Watch on Youtube</strong> to watch it there or
<strong>Decline</strong> to prevent video from being shown.
</p>
<div class="consent-nav">
<button class="consent-confirm">Accept</button>
<button class="close-consent">Decline</button>
<a href="https://www.youtube.com/watch?v=8jLNNMFur5A" target="_blank">Watch on Youtube</a>
</div>
</div>
</div>
<p>PS. I’m Team Ginger all the way.</p>
<p>
PPS. Don’t blame me if your productivity takes a dip and your happiness peaks
over the next weeks.
</p>
Handling JSON on command line
2023-10-11T00:00:00Z
https://hamatti.org/posts/handling-json-on-command-line/
<p>
JSON is one of my favorite formats for data as it balances human
readability/writeability with ease of parsing programmatically. In the modern
software development, at least on the web, we deal with JSON all the time,
whether it’s thanks to calling APIs, dealing with configuration, using JSON
data when building static sites or any of the other use cases.
</p>
<p>
One downside of JSON is that it’s verbose and even relatively small amount of
data becomes difficult to read and figure out on command line. Thankfully,
people have built tools that can help you become even more productive with
JSON.
</p>
<h2 class="notion-heading_2 notion-color-default">jq</h2>
<p>
First tool I want to introduce is
<a href="https://jqlang.github.io/jq/" class="notion-text-href">jq</a>,
<i class="notion-text-italic">a lightweight and flexible command-line JSON processor. </i>It’s a tool that uses its own language syntax that allows the user to write
compact one-liners that select, filter and manipulate the data provided to it.
</p>
<p>jq is written as a series of filters separated with pipe character |.</p>
<p>
Let’s look at how it works through an example. Here we have a snippet of
localization data in three languages.
</p>
<pre class="language-json"><code class="language-json">{
"languages": {
"fi": "Suomi",
"en": "English",
"sv": "Svenska"
},
"send": {
"en": "Send",
"fi": "Lähetä",
"sv": "Sänd"
},
"ordinals": {
"1": {
"en": "first",
"fi": "Ensimmäinen",
"sv": "första"
},
"2": {
"en": "second",
"fi": "toinen",
"sv": "andra"
}
},
"age": {
"en": "Age",
"fi": "Ikä",
"sv": "Ålder"
}
}</code></pre>
<p>
Let’s say we want to extract only the Finnish language values from it and
transform a three-language sub-object into a string value.
</p>
<pre class="language-bash"><code class="language-bash">jq 'walk(if type == "object" and has("fi") then .fi else . end)' < input.json</code></pre>
<p>
We use jq’s
<a href="https://jqlang.github.io/jq/manual/#walk" class="notion-text-href">walk</a>
function that recursively goes through the entire JSON structure. For each
item, we check if it is an object and has a key “fi”. If it does, we replace
that item with the value of key “fi” with
<code class="notion-text-code">.fi</code>. In jq, we refer to keys with syntax
that starts with a dot and follows with the key. If it is not an object or it
does not have the key “fi”, we “replace the item with itself” or in other
words, do nothing.
</p>
<p>This gives us the end result of</p>
<pre class="language-json"><code class="language-json">{
"languages": "Suomi",
"send": "Lähetä",
"ordinals": {
"1": "Ensimmäinen",
"2": "toinen"
},
"age": "Ikä"
}</code></pre>
<p>
As you can see, jq’s syntax is very powerful and compact but the learning
curve is quite steep and takes a lot to learn.
</p>
<p>
Since it uses a query based approach, the huge benefit is that it’s possible
to share these snippets in chats, forums, blog posts and such so they can be
reused. I have previously written
<a href="https://hamatti.org/posts/why-i-love-command-line/" class="notion-text-href">how that is one of the reasons I love command-line interfaces</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">jless</h2>
<p>
The second tool in my toolbox is
<a href="https://jless.io/" class="notion-text-href">jless</a> which is an
interactive JSON viewer for the command line. It uses vim keybindings to
traverse the data which can be a blessing or a curse depending on how
comfortable you are with using vim. For me, it made a big difference as I was
right at home with jless from the very beginning.
</p>
<p>
Piping a JSON object into jless launches it into an interactive session where
you can move around, collapse and expand objects, copy nodes, search with
regular expressions and a few other lovely things.
</p>
<p>
As vim has a quite a notoriously rough learning curve, I imagine getting
productive with jless can have similar learning curve for those not familiar
with vim.
</p>
<h2 class="notion-heading_2 notion-color-default">fx</h2>
<p>
The third tool offers tools similar to both jq and jless and it’s called
<a href="https://fx.wtf/" class="notion-text-href">fx</a>. Similar to jless,
when you pipe JSON data into it, it starts an interactive thing. But similar
to jq, you can also add arguments to the CLI tool call and it’ll run those
arguments sequentially to the data.
</p>
<p>
Where jq uses its own, sometimes bit cryptic looking syntax, fx supports
Javascript.
</p>
<p>
For example, if we’d get a list of objects with attribute
<code class="notion-text-code">name</code> and we want to find all the unique
ones, we can call <code class="notion-text-code">fx</code> with two functions:
first one maps through all objects to pick only the name to return and the
second uses fx’s built-in uniq function to filter out duplicates.
</p>
<pre class="language-bash"><code class="language-bash">fx 'map(item => item.name)' uniq</code></pre>
<p>
I’m still new to fx and one thing I haven’t managed to figure out yet is how
to do recursive functions like I used in the earlier example with jq. Despite
spending half of an afternoon trying to come up with a solution, I couldn’t
come up with anything and at one point I started to notice that the possible
outcome, if I came up with one, would have been overly complicated compared to
the jq one.
</p>
<p>
Googling for fx related things is very hard since fx (or f(x)) is a
mathematical concept and either there’s not a lot of content to be found or
it’s hard to find them from amidst all the math content.
</p>
<p>
So if you’re familiar with Javascript, using fx might be more productive and
easier to get start with for you compared to jq. For the interactive mode, it
supports similar vim-style keybindings as jless.
</p>
<h2 class="notion-heading_2 notion-color-default">Wrap up</h2>
<p>
I keep all these three in my toolbox to speed up my processes. Sometimes I
find something easier and faster to write with jq’s processing and syntax
style and sometimes it’s the case with fx.
</p>
<p>
<strong>edit 2023-12-03</strong>: I discovered there's also
<a href="https://github.com/01mf02/jaq">jaq</a> and
<a href="https://github.com/yamafaktory/jql">jql</a>. I haven't tried them out
yet myself.
</p>
<p>
Any other great JSON tools that I have missed in my life? Share them in the
Mastodon post below and let’s build our collective knowledge!
</p>
Document your secrets
2023-10-04T00:00:00Z
https://hamatti.org/posts/document-your-secrets/
<p>
It’s uttermost important to be vigilant about the
<i class="notion-text-italic">secrets</i> in your software project. API keys,
tokens and other secrets should never enter version control (or they will
eventually leak by accident). A common way to store secrets is a combination
of an <code class="notion-text-code">.env</code> file that is never checked in
and a <code class="notion-text-code">.env.sample</code> file that only
contains the structure of the file with no actual content. When you then start
a new project, you copy <code class="notion-text-code">.env.sample</code> to
<code class="notion-text-code">.env</code>, fill in the secrets and you’re
good to go.
</p>
<p>
Well, at least if someone in the project remembers what those secrets were and
how you’d get them. Sometimes there’s documentation somewhere: in the readme
file, in the internal wiki or on a post-it on the most senior developer’s desk
somewhere.
</p>
<p>
We touch those secrets so rarely that unless we bring in new developers into
team every month, we’re bound to start forgetting: what does this key actually
mean, where do you get it, is it personally generated or shared by the team
and what permission levels it should have in a system that supports permission
management.
</p>
<p>
Even my simple one-person static site webpage project has six different
environment variables and if I wouldn’t document them, I most certainly
wouldn’t remember in 6 to 12 months where I can get new ones.
</p>
<h2 class="notion-heading_2 notion-color-default">
Documentation in .env.sample
</h2>
<p>
Here’s an older example section from my
<code class="notion-text-code">.env.sample</code>:
</p>
<pre class="language-yaml"><code class="language-yaml"># Ghost CMS configuration
GHOST_URL= # The root URL to the domain where the CMS lives
# Ghost's Content API key, you can find it from
# Ghost Admin under Integrations under hamatti.org integration
# (see https://ghost.org/docs/content-api/#key)
GHOST_API_KEY= </code></pre>
<p>
When I fetch blog posts from my Ghost CMS to my static site, I need two
environment variables: the URL from where my CMS lives and the API key. They
are two very different type of values in nature: the URL does not necessarily
need to be secret and the knowledge of what the value is, is determined by
where I host my CMS – hence it lives in my head (and or documentation). On the
other hand, the API key absolutely must be secret as it has power to deal with
my CMS contents and it can be (re)generated via Ghost’s admin panel.
</p>
<p>
If you only have one or two that need generating, you might remember where to
find them. But most software projects I’ve been working on have half a dozen
at minimum. It becomes vitally important to document as detailed as possible,
where to generate these secrets.
</p>
<p>There are (at least) three things to document for an environment value:</p>
<ol class="notion-numbered_list">
<li class="notion-numbered_list_item notion-color-default">
<b class="notion-text-bold">What does it do or enable?</b> You may have keys
that turn on/off features and some sections or integrations might not be
needed in local development environment other than in very specific use
cases. Make it easy for developers in your team to know how that is.
</li>
<li class="notion-numbered_list_item notion-color-default">
<b class="notion-text-bold">Where to find it or generate it? </b>Being
specific here helps a ton. Documenting that something is “generated in AWS”
will lead to nothing but a wild goosehunt. Linking to platform’s
documentation is a good idea since if something changes in the UI where they
are generated, their documentation is hopefully updated too.
</li>
<li class="notion-numbered_list_item notion-color-default">
<b class="notion-text-bold">What permissions should be enabled? </b>For
integration keys that enable permission management, I find it valuable to
specify in documentation what is the minimum set of permissions that should
be given to a key. This avoids issues down the line when you realize someone
accidentally used a key that has power to do everything it and it caused
accidents.
</li>
</ol>
<p>
Writing documentation at the right detail is hard. I struggle with it every
day but I aim to become better. One thing that often makes me struggle when
reading documentation is when something states
<i class="notion-text-italic">what</i> something is rather than
<i class="notion-text-italic">what it should be in this context</i>.
</p>
<p>
An example of what I consider really good is this from
<a href="https://github.com/outline/outline" class="notion-text-href">Outline project</a>:
</p>
<pre class="language-yaml"><code class="language-yaml"># Specify what storage system to use. Possible value is one of "s3" or "local".
# For "local", the avatar images and document attachments will be saved on local disk.
FILE_STORAGE=local</code></pre>
<p>
I like that it specifies what the possible values are and what the
consequences are. For completeness, it could also state where the images and
attachments are stored when the value is set to “s3”, especially as there are
S3 related environment values in the file as well. But regardless it’s very
good and one of the better comments I’ve seen on
<code class="notion-text-code">.env.sample</code> files.
</p>
<h2 class="notion-heading_2 notion-color-default">Inspiration</h2>
<p>
If you want to see some inspiration, I recommend
<a href="https://github.com/search?q=path%3A**%2F.env.sample&type=code" class="notion-text-href">searching GitHub with query </a><code class="notion-text-code"><a href="https://github.com/search?q=path%3A**%2F.env.sample&type=code" class="notion-text-href">path:**/.env.sample</a></code>
to see how different projects deal with these files.
</p>
<p>
There you can find examples of both completely undocumented samples (do a
test: how many of them are ones where you can confidently say you know what
should go there and where to find it) and some that are nicely documented.
</p>
Traveling Europe on land: Turku to Prague
2023-09-27T00:00:00Z
https://hamatti.org/posts/traveling-europe-on-land-turku-to-prague/
<p>
As someone who doesn’t fly, living in Finland makes traveling in Europe a bit
challenging. While I lived in Berlin, I truly enjoyed the privilege of being
able to jump on a train and be in a different country later the same day. Now
that I’m back in Finland, there are a few extra hoops.
</p>
<p>
This month I traveled from Turku, Finland to Prague, Czechia by the sea and
the land and here’s my diary of the journey.
</p>
<h2 class="notion-heading_2 notion-color-default">Sunday, Sept 10th</h2>
<p>
<i class="notion-text-italic">Public transport: ~20 min buses to railway station; 2h in train; 20 min in
subway; 15 min bus to ferry terminal; 9 hours in ferry</i>
</p>
<p>
My alarm clock rings at 6am (and 6.15am and 6.30am… I’m not great at waking
up) as it’s time to head out for an adventure. The goal of the day is to board
Finnlines’ ferry towards Travemünde.
</p>
<p>
I take two buses from home to reach the Kupittaa railway station. Everything
is so quiet and still at 7am on Sunday morning. I enjoyed that peace and quiet
and being alone as the next couple of weeks are everything but that.
</p>
<p>
While waiting for the train to leave, I grabbed a sandwich from the hotel
deli. The more connections there are, the more waiting there is as I want to
make sure I don’t miss any of the individual trips as that can snowball into
issues.
</p>
<p>
The train to Helsinki takes roughly two hours. That means two episodes of
Virgin River from Netflix and one episode of Only Connect. Since the dinner is
served at 18.30 on the boat, I decided to eat lunch already at 9 in the
morning: meatballs and smashed potatoes on the train. Delicious as always.
</p>
<p>
Thanks to reduced trains in Sunday morning, I have 1.5 hours to spend in
Helsinki. Luckily, a great friend of mine came to hang out with me so we
headed for morning cinnamon buns in Helsinki’s wonderful Oodi library and its
summer terrace.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/1.png" alt="Two photo collage: on left, a cinnamon bun on a plate with the Finnish Parliament House on the background. On right, a photo of an overexposed polaroid photo of me sitting on the outdoor rooftop terrace of Oodi. " />
<p>
Continuing from Oodi, I withdrew some cash from an ATM as I’m heading to
Germany and need cash for a few things. The subway from Helsinki Railway
Station takes roughly 30 minutes to Vuosaari where I need to continue on a
15-min bus ride from the station to the ferry terminal. Finally, to reach the
ferry, there’s a shuttle bus from the terminal.
</p>
<p>
Around 13.30, I’ve got my bags in my cabin and I’m sitting at the pub writing
a few blog posts. The ferry to Travemünde takes around 30 hours and there’s
not a lot to do. I absolutely love it. I usually read books, sleep a lot, eat
well and sometimes play games, write blog posts and educational materials or
even code – anything I can do without access to Internet.
</p>
<p>
Steam Deck is a godsend on trips like these. I bought
<a href="https://store.steampowered.com/app/1244090/Sea_of_Stars/" class="notion-text-href">Sea of Stars</a>
for this trip (although, it was so good I ended up playing almost 10 hours
before leaving as I needed to test it works) and it’s rare to get this long
stretch of time in life to focus on a single thing with no responsibilities or
distractions. I can’t access Slack or Discord so I’m protected from myself.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/2.png" alt="A laptop with a slide about regex on it. On the background, a ferry’s pub and empty lobby " />
<p>
And since I was heading to PyCon CZ, I used part of the trip to crafting
slides for my potential talk.
</p>
<h2 class="notion-heading_2 notion-color-default">Monday, Sept 11th</h2>
<p>
<i class="notion-text-italic">Public transport: 21 hours in ferry; 20 minute bus to Lübeck</i>
</p>
<p>
I had such a good sleep on the ferry. As there are no windows and the cabin
stays absolutely dark throughout the night and morning, it’s easy to lose
track of time and keep sleeping. The ocean was also very kind throughout the
entire trip so there was no swinging on the waves.
</p>
<p>At the brunch, I overheard a beautiful description of this trip:</p>
<blockquote class="notion-quote notion-color-default">
“I’ve been to the gym, shower and had dinner and it’s only been 1.5 hours. And
there’s no Internet.
</blockquote>
<p>
This trip had the best buffet I’ve had on this ferry. Often their offering is
very seafood heavy and I’m not the biggest fan but this trip all the meals had
very good meat options as well. It’s honestly not the best trip to take if
you’re a vegetarian or vegan though.
</p>
<p>
Monday I spent mostly on reading blog posts from my RSS reader and continuing
the exploration of Sea of Stars. I also crafted a lightning talk slides for
PyCon as I really love the format and I have a quite fun one about comparing
version numbers.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/3.png" alt="The sunset over the Baltic Sea and a few people sitting on deck chairs on the deck of the ferry, watching the sunset " />
<p>
As the ferry arrived to Travemünde after 21, I grabbed my stuff, took the
shuttle bus to the terminal and caught a bus to Lübeck for a night. This part
of the trip I’m so used to. When I first took this route, I was so nervous as
there’s no signage for how to get out from the terminal area but these days
I’m happy to be comfortable with all this.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/4.png" alt="A hotel room in Niu Rig. There’s a bed, a window with curtains and a tv on the wall. " />
<p>
My hotel of choice in Lübeck is
<a href="https://the.niu.de/en/hotels/germany/luebeck/the-niu-rig" class="notion-text-href">Niu Rig</a>
which has small but comfy hotel rooms and it has two important features: one,
it’s next to the bus terminal where the Travemünde bus arrives and two, it’s
next to the main railway station where my morning train leaves.
</p>
<h2 class="notion-heading_2 notion-color-default">Tuesday, Sept 12th</h2>
<p>
<i class="notion-text-italic">Public transport: 30 min train to Hamburg; 7hr train to Prague; 20 min tram
to hotel</i>
</p>
<p>
I woke up at 6 in the morning to prepare for the day ahead. It was bit too
early but I prefer to be bit early and wait than have to rush or miss
connections. I took an RE80 local train from Lübeck to Hamburg and I’ve never
been in such crowded train on that journey, I might have hit the commute rush
hour.
</p>
<p>
In Hamburg, I had an 90 minute wait before my train to Prague would leave and
I spent it well listening to
<a href="https://areena.yle.fi/podcastit/1-65880497" class="notion-text-href">Finnish radio play of Vesa-Matti Loiri’s life</a>. All together that play is 20 episodes, 20 minutes each so trips like these
are the only way I’ll actually find time to listen to them. I finished the
play later in the evening and I have a few others to listen on my way back
home.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/5.png" alt="An on-going three player Skip-Bo game on a train table " />
<p>
The train from Hamburg to Prague takes roughly 7.5 hours so it’s a long but
steady trip. This time I got super lucky though: on the other side of the
corridor, there was two travellers who started playing Skip-Bo and after
watching for a bit and gathering courage, I asked if I could join them. They
say yes! We ended up playing Skip-Bo and 6 Nimmt! for hours and chatted about
life. Making new friends with Patrick and Laura was definite highlight of the
trip and made that long train ride fly by.
</p>
<p>
After they left, I finished listening to that radio play and after 58 hours of
travel, I arrived to the beautiful Prague.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/6.png" alt="A photo taken from the train with a river and a river boat and on the other side of the river, a patch of land with trees and hills rising from beyond that land " />
<p>
I’ve never had this smooth travel from beginning to the end. Everything went
near perfect. The Prague train was about 10 minutes late at the start but
that’s nothing.
</p>
<p>
In Prague, I used the public transport for the first time (previous times I’ve
either walked or taken taxis everywhere) and the newer trams here were really
nice. Very modern, very spacious feeling and had great displays for the
following stops. I wish Finnish cities would have similar ones.
</p>
<p>
After settling in, I headed out to eat and got some company as Honza joined me
for drinks. On his recommendation, I went to a food court called
<a href="https://www.manifestomarket.com/prague/andel/cs/" class="notion-text-href">Manifesto</a>
that had at least a dozen different restaurants, shared space for sitting down
and live piano music.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/7.png" alt="A burger with fries and a can of coke. " />
<p>
That’s one thing I love about traveling for conferences. Even if you don’t
know anyone in the city, you can find company from other participants and
organizers and speakers. PyCon CZ had a Discord where I kept chatting as I
travel (a tip I shared in
<a href="https://hamatti.org/posts/shy-introverts-short-guide-to-speaking-in-conferences/" class="notion-text-href">Shy introvert’s (short) guide to speaking in conferences</a>) and found Honza to both recommend that great place for me but also join me
for the evening.
</p>
<p>
After getting few first bites of the food, I started to notice the tiredness
from travel settle in and ended up getting back to the hotel after the dinner
and few drinks.
</p>
<h2 class="notion-heading_2 notion-color-default">Wednesday, Sept 13th</h2>
<p>
<i class="notion-text-italic">Public transport: Few tram trips across Prague, maybe 60 mins total</i>
</p>
<p>
Wednesday was full of action! I did get to sleep in which felt amazing as I’m
definitely not a morning person. I wrote some launch announcement posts and
emails to launch our
<a href="https://turkufrontend.fi/" class="notion-text-href">Turku <3 Frontend</a>
meetup registration and published my weekly blog post title
<a href="https://hamatti.org/posts/pull-requests-are-great/" class="notion-text-href">Pull requests are great</a>
which is a reply to online discussion I’ve been seeing lately.
</p>
<p>
At 13, I had lunch booked with some friends. I met Honza originally in PyCon
CZ 2019 and everytime I visit the city, I try to make sure I make time to grab
lunch with him. He works on a Czech developer community
<a href="https://junior.guru/" class="notion-text-href">junior.guru</a> and
chatting with him is always super inspiring. And he takes me to these great
lunch places in Prague. Today we also had Domitil join us. She’s doing her
first conference talk this weekend,
<a href="https://cz.pycon.org/2023/program/talks/92/" class="notion-text-href">talking about the Namibian Python community</a>. We had great lunch and then had a practice run of Domitil’s talk.
</p>
<p>
In the afternoon, I went to
<a href="https://www.kralovstvi-zeleznic.cz/" class="notion-text-href">Království Železnic</a>, a model railway museum that had two floors full of beautiful scenery with
model railways (and a few other transportation related items as well). I
didn’t manage to take any good photos unfortunately since the sceneries were
understandably protected by glass.
</p>
<p>
For the rest of the evening, I mostly spent in BeerTime pub tasting local
beers (a shoutout to
<a href="https://untappd.com/b/pivovar-zhurak-idaho-haze/5279487" class="notion-text-href">Zhůřák’s Idaho Haze</a>) and eating well. While there, I wrote some advertisement posts for the
upcoming
<a href="https://www.syntaxerror.tech/" class="notion-text-href">Syntax Error newsletter</a>
that comes out this Sunday, Sept 17th. I also worked quite a bit on editing
the newsletter issue itself.
</p>
<p>
It’s surprisingly challenging to come up with example bugs that are not
immediately obvious and trivial to spot. I think I managed to come up with one
for this month’s newsletter and I’m quite excited as I got to write a story of
a bug and how to debug it. If you’re a software dev, head over to
<a href="https://syntaxerror.tech/" class="notion-text-href">the site</a> and
subscribe either via email or RSS and you’ll get your newsletter delivered on
Sunday.
</p>
<p>
In the evening, I headed back to hotel to catch up with what’s been happening
in the Interwebs. Mainly, watching the newest episodes of Ahsoka and Only
Connect and checking out a few new Youtube videos from channels I follow.
</p>
<h2 class="notion-heading_2 notion-color-default">Thursday, Sept 14th</h2>
<p>
Thursday is mostly a holiday-day. The only thing I have booked in my calendar
for the day is the speaker dinner (my favorite perk of being a speaker) in the
evening. I slept around the clock and spent the morning in bed watching more
of Youtube.
</p>
<p>
I also did some community stuff: I’m currently running an annual salary survey
for
<a href="https://koodiklinikka.fi/" class="notion-text-href">Koodiklinikka’s community</a>
and this year we’ve already got more than 800 replies which is a new record
and quite an impressive amount of data from salaries and billing rates from
the Finnish IT industry. I made some new memes to promote the survey in our
Slack and answered questions when some arise throughout the survey. I’m really
hoping we’d get to 1000 participants but I’m not holding my breath as we’ve
already had our two main spikes.
</p>
<p>
I’m also working on the finalizing touches of our fall schedule for Turku <3
Frontend meetups and had a brief discussion with sponsors and speakers for the
upcoming events. So happy to see that the interest towards the community is
ever increasing.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/8.png" alt="A yellow cocktail on a glass, a Kindle with a book open on it and on the background, some people sitting on chairs enjoying life. " />
<p>
For afternoon, I headed out for cocktails, lunch and some reading. I’m
currently reading
<a href="https://www.goodreads.com/book/show/7155145-linchpin" class="notion-text-href">Seth Godin’s Linchpin</a>
that was recommended to me in
<a href="https://koodarikuiskaaja.fi/" class="notion-text-href">Koodarikuiskaaja’s community</a>
during our discussion of glue work (that was inspired by
<a href="https://www.youtube.com/watch?v=KClAPipnKqw" class="notion-text-href">Tanya Reilly’s great talk on the topic</a>). I’m roughly half way through and the book is annoyingly
<i class="notion-text-italic">sethgodin </i>in its controversial, provocative
and very American storytelling but it does have some good thoughts on it so
I’m trying to filter the good stuff through.
</p>
<p>
This trip has real got me thinking about life. I’m so happy to be privileged
and lucky enough to be able to travel like this on a regular basis and to make
friends from all corners of the world. One of the biggest changes lately has
been how easy and straight-forward this has become for me.
</p>
<p>
Travel used to be this Big Thing where you prepare and get bit nervous and go
somewhere exotic. But now it’s just another stop at the end of a slightly
longer train trip than domestic travel. I have friends in pretty much every
bigger city in Europe so I’m always able to find friends to hang out with and
I feel confident enough to survive even in countries where I don’t speak the
local language.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/9.png" alt="An outdoor cafe with a few empty tables and a lovely brick wall on one edge and wooden fence on another one, hiding the cafe from the surrounding areas. " />
<p>
I also got to enjoy a drink in a nearby cafe that I found by accident. It was
well hidden from the street. The place is called
<a href="https://www.kavarnacohledajmeno.cz/" class="notion-text-href">Kavárna co hledá jméno</a>
(which translates to
<i class="notion-text-italic">Cafe looking for a name</i>) and I could have
sat there for hours.
</p>
<p>
In the evening, we had the speakers’ dinner – which I consider to be the best
perk of being a speaker – and it was so lovely. There were so many people from
the 2019 PyCon CZ conference that I had met back then and many more who
remembered me and my talk from over 4 years ago. And some new friends that I
got to make and have lengthy discussions with about board games, history,
politics and travel.
</p>
<h2 class="notion-heading_2 notion-color-default">Friday, Sept 15th</h2>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/10.png" alt="An old monastery Gabriel Loci on a sunny day " />
<p>The first conference day!</p>
<p>
I had such a blast day. I made a few new friends over breakfast, enjoyed a
bunch of talks (I’ll write a proper PyCon CZ Recap blog post a bit later) and
just felt really great throughout the day.
</p>
<p>
The venue at
<a href="https://www.gabrielloci.com/en/" class="notion-text-href">an old monastery of Gabriel Loci</a>
was absolutely stunning and it was made even better by the weather. Warm but
not too hot, nice sunshine and plenty of space outdoors around the venue to
chill out during breaks.
</p>
<p>
I got into so many great discussions about technology, company culture,
documentation, learning & teaching, community building and board games
throughout the day with so many people from all around the world.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/11.png" alt="A full main hall at the conference. Sponsor rollups on both sides of the stage and a conference Q&A instruction slide on the main screen. " />
<p>
I signed up to do a lightning talk and it went super well: people laughed at
my jokes at the right time and I even got a mid-way applauds during a 5-minute
talk. There’s no better feeling than being on the stage. And despite adding a
few things from my previous runs of the talk, I managed to finish it at 4
minutes 57 seconds – my new superpower is to actually hit the time slot
provided and making the most out of that time.
</p>
<p>I also got some lovely feedback:</p>
<blockquote class="notion-quote notion-color-default">
Loved your lightening talk about version numbers
</blockquote>
<blockquote class="notion-quote notion-color-default">
Your talk was one of the highlights of the whole PyConCZ for me and it was
definitely the best lightning talk I've ever heard.
</blockquote>
<blockquote class="notion-quote notion-color-default">
Great talk by the way. Excellent flow, beautiful slides, just enough humor
</blockquote>
<p>
If I think about what makes community a true community and not just people
showing up to events, I believe it is when people participate in helping out
make things happen even when not asked. The board game night after the first
conference day took place in the main conference room and it was lovely to see
conference participants help organizers out with carrying chairs out of the
way and carrying tables to make a huge space for playing games.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/12.png" alt="Me on a stage in front of a few hundred developrs sitting in the main hall of the conference " />
<p>
That’s something you can’t buy with money and that is why building communities
is such a valuable and worthwhile effort. It takes time, it takes true
participation and caring so it can be hard to justify and do in a short-term
gain capitalism. The results are not immediate and often the best results
cannot be forced into a metric, no matter how hard some people want to try.
</p>
<p>
I got to play
<a href="https://boardgamegeek.com/boardgame/34635/stone-age" class="notion-text-href">Stone Age</a>
with a few locals and learned a new game in
<a href="https://boardgamegeek.com/boardgame/199561/sagrada" class="notion-text-href">Sagrada</a>. Stone Age is one of my favorite games because it’s an easy to approach
worker placement game that is easy to learn, doesn’t require heavy math and
planning but still offers enough strategic depth to not be random.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/13.png" alt="A game of Stone Age set up for four players with a main board, each player with their own player board and colored meeples and a lots of wood, clay, stone, gold and food tokens on the board. " />
<p>
Sagrada on the other hand, oh wow. I have never felt that kind of simultaneous
frustration and joy when playing a game. It’s deviously difficult game. You
place different colored dice into your grid following certain rules and all
the time it feels like the dice are against you, the board is against you and
everything is just so hard. But it’s a fun game, I had a blast learning it
despite saying a few well-placed curse words aimed at the dice. Imagine
needing a yellow or purple 2 for 5 rounds and none show up until the last
turn.
</p>
<h2 class="notion-heading_2 notion-color-default">Saturday, Sept 16th</h2>
<p>
Early bird catches the breakfast bagel. I was prepared to give myself a chance
to sleep in a bit but ended up waking way before my alarm clock and headed to
the venue. The breakfast offering for both days was so good and so many people
came to chat and compliment my talk from yesterday.
</p>
<p>
I do have a Syntax Error newsletter to send tomorrow and I’ve been writing,
editing and fine-tuning it over many small breaks during the week and it’s
mostly done by Saturday morning. I’ve been thinking about the future structure
and content of the newsletter quite a bit recently as I ran out of the initial
ones I had written mostly in last December. This month I decided to try
something a bit different, interesting to see how people like it.
</p>
<p>
The second day of the conference is always a bit slow start for me. The
speaker dinner the night before the conference and the first day are filled
with so much social interaction. In addition to that, giving a talk at the end
of the day gave such a rush of good feeling and excitement that in the next
morning I feel like my social energy batteries are out of charge.
</p>
<p>
Slowly throughout the day, I picked up the energy levels and caught a few
great talks and had even more lovely discussions. I’ll speak more about the
talks in an upcoming conference recap blog post.
</p>
<p>
As the night started to fall over Prague, I got to enjoy live campfire while
having great discussions about poetry and art and linguistics. A nice aspect
of these conferences is that everyone is more than just a developer so you end
up having chats about more than just code and projects.
</p>
<h2 class="notion-heading_2 notion-color-default">Sunday, Sept 17th</h2>
<p>
<i class="notion-text-italic">Public transport: 60 min trams to and back from workshops</i>
</p>
<p>
Sunday’s the workshop day! I almost slept too long and was too slow to leave
the hotel but barely made it to the workshops in time. Luckily, I ended up
having booked the same two workshops as Simona who I met during the board game
evening and again on Saturday’s after party. So I had a familiar face there
and we ended up pairing on both workshops.
</p>
<p>
Daniel Raniz Raneland’s Test Driven Development For Everyone workshop was a
nice opportunity to practice more test-driven development while pair
programming. Pairing with someone when coding is so much fun and I’d love to
do it even more. Having two brains think about the same problems at the same
time brings with it some amazing superpowers.
</p>
<p>
In the afternoon, Daryl Seager’s Effective Communication was another great
workshop. Nothing particularly new to me as I’ve been to a few of
<a href="https://greenelephant.org/" class="notion-text-href">Green Elephant’s</a>
communication workshops and plenty of therapy to practice self-reflection in
my past but getting to practice that with new people was definitely worth it
and Daryl’s workshop was spot on.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/14.png" alt=" " />
<p>
To finish the conference week, I headed back to Manifesto food court for some
chicken tikka masala and spent the night in the hotel chilling. After 4 long
days of socializing and making new friends, I was so happy to spend a few days
completely alone.
</p>
<h2 class="notion-heading_2 notion-color-default">Monday, Sept 18th</h2>
<p>
The second “day off” during this holiday trip came at a good time. Before
heading back home, I had an entire day to just chill so I spent most of it in
Manifesto again for some cocktails, reading books, writing code and planning
the future. I started building a Django website for a secret future project
and ate and drank well.
</p>
<p>
That was pretty much the whole day. I went to relax in the hotel and watched a
few movies in the middle before heading back in the evening for evening snack.
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/15.png" alt="A stop display in a tram showing line 12 and a dozen or so next stops on the way " />
<p>
One of the things I really liked in Prague’s trams were these displays that
showed not only the next stop but a lot of the upcoming ones and the final
destination too. This is so helpful for a tourist who might not know all the
stops on the way but finding your final one on the board helps relax and
assess the situation.
</p>
<h2 class="notion-heading_2 notion-color-default">Tuesday, Sept 19th</h2>
<p>
<i class="notion-text-italic">Public transport: 20 mins tram to railway station; 7 hrs train to
Hamburg</i>
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/16.png" alt="Vltava river photographed from a tram, showing a few beautiful buildings in Prague and the forest hills in the horizon. " />
<p>
Time to start heading home! I checked out of the hotel, headed out to the
railway station, had a French baguette for breakfast and boarded the train
towards Hamburg. This time, I didn’t find board game company to play with
during the trip but I listened to Tom Scott’s Lateral podcast for 7 hours
before reaching Hamburg.
</p>
<p>
I had booked a hotel right next to the railway station so I headed straight
there. I had a dinner at
<a href="https://www.ottosburger.com/" class="notion-text-href">Otto’s</a>
which is my go-to place in Hamburg for food. In the evening, I watched PSG -
Dortmund Champions League game and the newest MCU Spiderman movie before
hitting the bed.
</p>
<p>
Just before going to bed, I wrote
<a href="https://hamatti.org/posts/how-you-can-make-conferences-better/" class="notion-text-href">a blog post about conferences</a>
and noticed that my older Ghost CMS server had broken, returning 502 error
code for all requests - meaning all my images for a few years of blog posts
were now broken. Not sure how to fix it, I decided to sleep on it and hope it
would fix itself.
</p>
<h2 class="notion-heading_2 notion-color-default">Wednesday, Sept 20th</h2>
<p>
<i class="notion-text-italic">Public transport: 30 min train to Lübeck; 20 min bus to ferry terminal</i>
</p>
<img src="https://hamatti.org/assets/img/posts/traveling-europe-on-land-turku-to-prague/17.png" alt="A colorful drink on a bar with the drink selection of the bar in the background out of focus. " />
<p>
I got to sleep in but the cleaning crew came into the room twice in the
morning before I found a “Do not disturb” sign. It worked this time even
though I have bad experiences about them in Germany as multiple times they
have not been respected during my stays in various hotels.
</p>
<p>
The server problem hadn’t fixed itself so I did an emergency fix: I downloaded
all the images and ran a quick sed replace command to change URLs from the CMS
server to my local repository. That works for now but I still need to figure
out a better solution.
</p>
<p>
Then I got an email that my ferry will be delayed due to the storm in the
Baltic Sea. Instead of the ferry leaving at 3am, it’ll now leave around 7am
but I still need to be in the ferry terminal around 22 so it’ll be a long
night alone in the terminal.
</p>
<p>
And then I noticed that the trains from Hamburg to Lübeck were all cancelled.
After lunch, I headed to the railway station to learn that there was a bridge
on fire and all connections were cancelled for foreseeable future.
</p>
<p>
After pondering the problem for a while with a strawberry milkshake, I
realized I had seen trains leave from Lübeck to Kiel so I went to ask if I
could circumvent the problem by going to Kiel and then to Lübeck. “Sure, it
works” I was answered by the same person who 30 minutes earlier said there are
no alternatives. Customer service, eh?
</p>
<p>
Train to Lübeck, pizza and drinks at my go-to place in town while waiting for
the ferry, Time Out sports bar and more podcast listening and more blog
writing.
</p>
<p>
After an entire afternoon / evening in the bar, I took the bus to the
terminal. I’m not sure if it was the smartest move to get to a ferry terminal
roughly 11 hours before the boat leaves but it’s a decision I made.
</p>
<p>
Of course, what I didn’t account for was that there were no electric outlets
in the waiting room. So I’m in the ferry terminal roughly 11 hours before the
ferry leaves, my phone has 9% battery (and that’s my only source of Internet)
and I cannot charge my devices.
</p>
<p>
A nice thing they did however was to offer a chocolate bar, bottle of water
and one of those salami sticks to keep me alive during this delay. That was
genuinely nice gesture.
</p>
<p>
The thing is, I’m no hurry to get anywhere. It doesn’t matter if the boat
leaves 4 or 5 hours later than usual. I’ll still be home by Friday evening.
</p>
<p>
Eventually I got to the ferry, headed straight to my cabin and fell asleep
within minutes.
</p>
<h2 class="notion-heading_2 notion-color-default">Thursday, Sept 21st</h2>
<p>
<i class="notion-text-italic">Public transport: 21 hrs in ferry</i>
</p>
<p>
The day at the sea was mostly spent in bed. There was bit of heave of the sea
so I didn’t particularly enjoy being out in the open areas and I was tired
after travel so I didn’t manage to find energy for writing or coding. So I
decided to just watch MacGyver in my cabin bed and sleep a few naps between
meals.
</p>
<h2 class="notion-heading_2 notion-color-default">Friday, Sept 22nd</h2>
<p>
<i class="notion-text-italic">Public transport: 10 hours in ferry; 15 min bus; 20 min tram; 2 hr train;
20 min buses</i>
</p>
<p>
Time to go home. The ferry managed to catch up to its schedule during the trip
so despite late departure, we arrived in Helsinki in time and after a bus, a
subway, a train and two more buses, I got home and can call this trip a big
success and a lovely holiday.
</p>
<p>It feels good to be home.</p>
<p>
I’ll share a conference recap with some talk recommendations once the videos
are published in PyCon CZ’s Youtube channel.
</p>
How you can make conferences better
2023-09-20T00:00:00Z
https://hamatti.org/posts/how-you-can-make-conferences-better/
<p>
As I'm traveling home from PyCon CZ and ran into a blog post by Hynek
Schlawack titled
<a href="https://hynek.me/articles/hallway-track/" class="notion-text-href">The Price of the Hallway Track</a>
and I wanted to follow up on that.
</p>
<p>
In that blog post from 2019, Hynek criticizes a trend where people overly
promote hallway track in conferences over going to see the talks. While I'm
not saying don't go to any talks myself, I've been hyping up hallway track
myself in the past so I’ve definitely contributed to this without intending it
as such. It’s a great blog post and shines light on the topic from a different
perspective.
</p>
<p>
One thing I sometimes find hoping in conferences is a bit less crammed
program. I’ve been to some conferences that had a single room, single track
program with not enough breaks and no program after the last talk ends. It can
be hard socially hard to hallway track in those and I usually feel very tired
and unfulfilled from those events.
</p>
<p>
I've seen so many times people participate a conference (maybe their company
paid for it), they sit through every talk, feel exhausted by it and go home
unhappy with their conference experience. That's what has lead me to promote
the hallway track as the best part of conferences but it's important to
remember the downsides that Hynek writes about.
</p>
<blockquote class="notion-quote notion-color-default">
[—] it [not watching talks] also sucks for the audience both in the talk and
later on YouTube. Because the lack of butts in seats also means a lack of
energy and a lack of energy means a worse talk. The content will be mostly the
same but a speaker who doesn’t feel good on stage is not as much fun to watch
as one who is enjoying themselves.
</blockquote>
<p>
I can definitely agree on that. As a speaker, nothing kills a good talk faster
than an empty room. Early in my career, I once traveled 4 hours to talk to a
room with a single person.
</p>
<p>
I then followed through the original Twitter/X thread Hynek linked in the post
and saw
<a href="https://twitter.com/AllenDowney/status/1124341643937710082" class="notion-text-href">this tweet/xeet by Allen Downey</a>:
</p>
<blockquote class="notion-quote notion-color-default">
I agree! Can I piggyback with a related point? The "hallway track" is most
accessible to well-connected insiders. If you are not one of them, it can be
discouraging to hear how great it is. If you are one of them, please help
others connect.
</blockquote>
<p>
That is also so true and reading it sparked this blog post. So let’s tackle a
few ways you and I as conference attendees can make the conference a better
experience.
</p>
<h2 class="notion-heading_2 notion-color-default">
Making connections and introductions
</h2>
<p>
Let’s start with what Downey was speaking about. When I started attending tech
events, I didn’t know anyone and mostly sat in the corner of meetups eating
free beer and eating pizza, listening to talks and then going home. Eventually
some people noticed I’m always there and started talking to me.
</p>
<p>
These days I love the hallway track and I talk about how great it is but I
recognize it can be challenging to get into. So if you're in a conference with
friends or having a discussion with people you met during the conference,
actively invite new people to join those discussions and introduce people to
each other.
</p>
<p>
Eating the breakfast on the first morning of the event can be daunting if you
don't know anyone. If you see anyone standing around alone, ask if they'd like
to join you and introduce the people in your group to them. That will do
wonders to getting people into the "inner circle" of the discussion and
encourages people to do it.
</p>
<p>
Also, introducing your pre-existing friends to new people you meet in the
conference is a great way to grow everyone’s networks and help them make new
friends.
</p>
<p>
And if you're a speaker, go out of your way to hang out with more than just
other speakers. Not everyone has the social courage to approach speakers or
they might not know what to say so being open to discussions is a great way to
do lots of good.
</p>
<p>
When I attended Strata Conf 2014, I went to speak to one of the speakers who
was someone whose work I had followed with awe for a long time. I was so
nervous I pretty much messed up all my words but she was super nice and
handled it like a pro.
</p>
<p>
As a bonus, if you're able to have influence in your company on who gets to go
to conferences, bring a junior developer with you and introduce them to the
community and help them make connections and learn how to get the best out of
the conferences.
</p>
<p>
Hallway track is something that nobody’s born an expert in, it’s one of those
things people learn from other conference goers so let’s teach new comers how
to get the best out of it without telling them to skip all the talks.
</p>
<h3 class="notion-heading_3 notion-color-default">Follow the Pac-Man rule</h3>
<img src="https://hamatti.org/assets/img/posts/how-you-can-make-conferences-better/1.png" alt="Pac-Man" />
<p>
After sharing the first version of this blog post, I
<a href="https://mastodon.world/@flaki@flaki.social/111096936126681466" class="notion-text-href">learned from Flaki</a>
that there’s a thing called
<a href="https://www.ericholscher.com/blog/2017/aug/2/pacman-rule-conferences/" class="notion-text-href">Pac-Man rule</a>
that encourages people to make it easier for others to join discussions and
it’s brilliant.
</p>
<blockquote class="notion-quote notion-color-default">
The rule is: When standing as a group of people, always leave room for 1
person to join your group.
</blockquote>
<p>I love it! It’s easy to remember and easy to follow.</p>
<h2 class="notion-heading_2 notion-color-default">Nodding along</h2>
<p>
Talk to any speaker about this and they’ll probably all tell you it’s true. If
someone is in the audience and nods along while you talk, it makes such a huge
difference and is the smallest act of kindness someone can do to help a
speaker – no matter how experienced they are.
</p>
<p>
I absolutely love when I see someone in the audience who nods along when I
speak. Not only does it feel great to see someone being actively interested in
the talk but often if I get bit nervous during a talk or forget something, I
seek out those people to get a bit of social confirmation that everything's
gonna be good and it calms me down.
</p>
<p>
It feels like I’m talking to an actual human being when someone does that.
</p>
<p>
That's also why online talks are so difficult for me as a speaker. It's so
hard and discouraging to talk to a webcam with zero immediate visual feedback
from the audience.
</p>
<h2 class="notion-heading_2 notion-color-default">Chat with the speakers</h2>
<p>
Related to this, one of Hynek's great tips for encouraging especially newer
speakers is to chat with them after the talk:
</p>
<blockquote class="notion-quote notion-color-default">
Talk to speakers after their talks. Especially if the room was empty, consider
making them feel better after they gave their talk. Chat about the topic or
just tell them you liked their talk. Speakers usually get very little positive
feedback.
</blockquote>
<p>
I love when people come to talk to me after my talk. Especially since I’m a
shy introvert and it can be hard for me to approach new people. I had so much
fun last weekend when people came to chat with me and I met so many great
people and learned so much from them thanks to that.
</p>
<p>
And I know many of us do talks in conferences for this exact reason: to have
opportunities to chat about our work and passion with more people and to learn
from them and gain new perspectives.
</p>
<h2 class="notion-heading_2 notion-color-default">
Participate in the digital platform before and during the conference
</h2>
<p>
One thing I like to do personally to create positive vibes is to be active in
the digital platforms. Many conferences have Slack or Discord servers and
sharing things, asking for sightseeing or restaurant recommendations (or
inviting others to join) and giving feedback on talks and event practicalities
are all things that bring more life to the event.
</p>
<p>
If you're a speaker, making yourself available to questions in the digital
platform during the event can also help people by lowering the bar for people
to start discussions. Sometimes this is facilitated by the organizers
(although I think it's more common in online events) but usually nothing stops
you from sending a message “Hey, if anyone has any questions or wants to chat,
ping me here or find me in the event and come chat” after a talk.
</p>
Syntax Error #7
2023-09-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-7/
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this September issue of Syntax Error we look
do a bit of roleplaying to take a look at bug that sneaked into a system and how to debug it. Read full article at <a href="https://www.syntaxerror.tech/syntax-error-7">syntaxerror.tech/syntax-error-7/</a>and either subscribe to the email or RSS feed to catch all of them.
Pull requests are great
2023-09-13T00:00:00Z
https://hamatti.org/posts/pull-requests-are-great/
<p>
I have recently been seeing an increasing amount of chatter about and against
pull requests. These especially often come from the crowd that advocates for
pair or mob/ensemble programming. I saw a great one in Mastodon the other week
but failed to save it so I can’t reference it. In essence, that toot asked:
<b class="notion-text-bold">What legit benefit is there for pull requests for teams that trust each
other?</b>
</p>
<p>
And I’ve seen this sentiment quite often: some people consider that pull
requests’ main or even only function is to prevent malicious or bad code from
entering a codebase from untrusted sources. And in many distributed open
source projects that is one of its functions. However, I’d argue that focusing
solely on the trust issue and then dismissing pull requests for teams that
trust each other, is short-sighted.
</p>
<p>
I’m a fan of pair and ensemble programming and this is not a blog post
dismissing or refuting them. Often pair or ensemble programming end up being
the answer to many of the benefits of pull requests and for some cases, I
agree. I’m writing this post from the perspective of a team that doesn’t do
those as most teams don’t.
</p>
<h2 class="notion-heading_2 notion-color-default">Asynchronous development</h2>
<p>
Some people, like Gregor Riegler in his article
<a href="https://gregorriegler.com/2023/01/30/solo-programming-considered-bad.html" class="notion-text-href">Solo Programming Considered Harmful</a>
(it’s a great read!) consider working on features alone (within a team) a bad
thing:
</p>
<blockquote class="notion-quote notion-color-default">
It turns out that solo programming may not be as good as we think. No, I’m
serious. I think we overestimate our programming skills. We tend to
overcomplicate things, and we make a lot of mistakes. We rarely understand the
problem that needs to be solved, and we don’t know our tools well. We often
end up with crufty code that isn’t as simple as it could be. It doesn’t work
and often targets the wrong problem.
</blockquote>
<p>
These people have a point but I feel they often throw ensemble programming as
a one-size fits all solution and silver bullet without trade-offs worth
considering.
</p>
<p>
One thing that pull requests (and solo programming) enable is asynchronous
development.
</p>
<p>
Personally, I value flexibility in work a lot. Whether the teams is spread
across time zones or located in the same city, people can work at different
times of the day effectively. I’m currently doing 80% work week meaning I’m
out of the office on Wednesdays. That means I can wrap something up and create
a pull request on Tuesday and there’s some time for others to review my work
without having to wait for me to return.
</p>
<p>
Or if I’m working on European timezones and my team mate works in west coast
US, we can bounce off ideas and code reviews when the other one is away.
</p>
<p>
Asynchronous communication isn’t easy and it requires putting in the work to
learn and become effective with it but it can also open a lot of
opportunities.
</p>
<h2 class="notion-heading_2 notion-color-default">Learning opportunities</h2>
<p>
In a good team, I find pull requests more important for shared learning than
preventing bad code from entering the codebase. If you ensemble everything
with the whole team, that can work too for learning but it’s hardly a reality
for most teams, even to those that do some part of their work in groups.
</p>
<p>
Pull requests and code reviews are a great opportunity for the team to share
understanding of the codebase and how it’s evolving. They are often seen as a
“senior developer reviews junior’s code” but it can be so much more. Code
reviews on pull requests should not be seen as a judgement of an effort but a
(well crafted) starting point for a further discussion. Some part of the
discussion often needs to happen before decisions are made all the way to code
level but there’s still room for them in the pull requests.
</p>
<p>
I often read through my colleagues pull requests even if I’m not per-se
reviewing them as it helps me see what’s happening, to learn from their
approaches and style of solving problems and how they structure their code.
</p>
<h2 class="notion-heading_2 notion-color-default">
It’s a unit of work I can go back to
</h2>
<p>
Even if I was in a team that ensembles, I’d still like to wrap certain units
of work into a pull request. I do that all the time when I work alone on
projects: my changes go to pull requests and I sometimes even let them sit
there for a moment and then review my own code with fresh eyes.
</p>
<p>
Unless the entire session or feature is worked into a single commit, I find it
valuable to be able to go back and look at the development as a unit that can
be easily found from a pull request.
</p>
<p>
Recently, as a new person in the team, I’ve implemented multiple features
where there’s been a previous similar approach used in another feature. Being
able to open an old pull request with well crafted step-by-step commits helps
me a lot in understanding what goes into building this feature and I’m able to
see what parts are relevant to mine and what are not. I can even see the
comments in the review and the process that it has gone through.
</p>
<h2 class="notion-heading_2 notion-color-default">
Written code reviews are documentation
</h2>
<p>
One thing that grinds my gears is silent knowledge in teams and organizations.
Teams change, people move on to other projects or companies and new people
join teams.
</p>
<p>
I’m a firmly believer that code reviews, even if they’re done in-person with
someone, should be written down as documentation to pull requests. Decisions
made (even if they deliberately lead to non-action) are valuable information
for the future moments and having those discussions written down ensures they
are saved for the future.
</p>
<p>
While most documentation we want to keep up to date to be valuable, there’s
value in documentation thoughts, reviews and decisions as-is, frozen in time
and version control is a great tool for this.
</p>
<p></p>
My iterative approach to software development
2023-09-06T00:00:00Z
https://hamatti.org/posts/my-iterative-approach-to-software-development/
<p>
The more I’ve worked with different people in software development, the more
I’m fascinated by how different approaches people have to building software. I
love seeing other approaches than mine and picking the best parts of those to
improve my own workflow.
</p>
<p>
I’ve been thinking about my approach in contrast with other devs and
especially with unspoken expectations from non-developers. By expectations, I
mean if someone’s used to a certain way of developers’ work, a new differing
way can cause communication issues if not discussed.
</p>
<p>To illustrate this blog post, I made these beautiful pictures in Keynote:</p>
<img src="https://hamatti.org/assets/img/posts/my-iterative-approach-to-software-development/1.png" alt="Illustration with a large brown box and near the bottom, is a red line and on the top of the box is a stick figure " />
<p>
That’s the starting point. I’m at the top, there’s a vast brown unknown and
somewhere below there, at an unknown depth is the red goal line. My confidence
level of if I can reach the red and how fast, varies wildly between features.
</p>
<p>
Almost always my first goal is just to reach the red line, no matter what.
Just to make sure it exists and that I know the direction where to go. What
“reaching the red line” means, is usually a quick-and-dirty prototype that
achieves one main part of the functionality.
</p>
<img src="https://hamatti.org/assets/img/posts/my-iterative-approach-to-software-development/2.png" alt="Same illustration as before but this time there’s a narrow white rectangular whole from the top of the box to the red line " />
<p>
So the first iteration is very narrow and to the point. It helps with two
things: I can quickly see where the goal is, can I reach it and I can start
demoing it with interested people to see if the red line was correct one and
where to go from there.
</p>
<p>
In my experience, this is quite common in agile development at least amongst
the devs I’ve worked with. You start with something and then iterate to make
it better.
</p>
<img src="https://hamatti.org/assets/img/posts/my-iterative-approach-to-software-development/3.png" alt="Brown box, red line near the bottom and a white cutout from the top that doesn’t quite reach the red line. There are grey vertical small boxes at the edges of the white cutout with labels “Supports” " />
<p>
Once I then start the next iteration, I essentially break what used to work.
While the next iteration is on-going, I don’t usually have a way to demo the
progress anymore. I can always go back to the end of first iteration and demo
that but the progress from there onwards is not linear, it’s rather
<a href="https://www.youtube.com/watch?v=q2nNzNo_Xps" class="notion-text-href">wibbly-wobbly</a>. Now that I know and am confident I can reach the goal, I’m confident I
don’t need to be able to reach it all the time and I can start building again
from scratch (or almost from scratch), this time with better supports. These
supports usually mean better code, better infrastructure, tests and so on.
</p>
<p>
It’s going backwards in progress to make it easier to end with a better
result.
</p>
<img src="https://hamatti.org/assets/img/posts/my-iterative-approach-to-software-development/4.png" alt="Brown box, red line near the bottom and a white cutout from the top all the way to red line. There are grey vertical small boxes at the edges of the white cutout with labels “Supports” " />
<p>
At the end of the 2nd iteration, I have once again reached the red goal line
but this time there’s more there. Maybe I’m connecting the improved prototype
to some existing production-level integration and started prototyping on that
end too.
</p>
<img src="https://hamatti.org/assets/img/posts/my-iterative-approach-to-software-development/5.png" alt="A brown box, red line near the bottom. There’s a green tent on top of the box and a crane that lowers a platform with a stick figure into a white cutout that goes halfway towards the red line. " />
<p>
And then I jump on to the next iteration, once again losing sight of the goal.
By writing something once and then discarding it rather than trying to change
it in-place, I usually come out with way better architected solutions with
cleaner and more understandable code and better user flows.
</p>
<p>
Every time I’ve reached the goal, my map of what’s on the path gets more
detailed and clearer, making it easier to craft better paths towards it. But
once again, while I’m crafting the new path, I might not have anything that
works until I reach the goal line again.
</p>
<p>
And quite often, the length of an iteration ever so slightly increases during
these because I’m putting more care and effort into them.
</p>
<img src="https://hamatti.org/assets/img/posts/my-iterative-approach-to-software-development/6.png" alt="Brown box, a white cutout from the top all the way to the red line near the bottom. A green tent on the top and a stick figure at the bottom of the cutout. " />
<p>
This is why I like the lean and iterative approach. I’ll be honest with you:
it’s very rare that at the beginning of a feature I’m confident enough that I
can build it right on the first try. So I very rarely try to make it perfect
from the start.
</p>
<p>
Usually this process from first iteration to “completed” (is anything ever
completed in software world?) lives within a pull request cycle and can be
done all within a day with multiple iterations if a feature is small.
</p>
<p>
But 99% of the time, when I start developing something, the first version is
not code I’d ever push to production. I may create a pull request on it so I
can gain feedback on the idea on technical level early on but there are always
multiple rounds of iteration that I go through as I craft solutions.
</p>
Blaugust 2023: Lessons Learned
2023-08-31T00:00:00Z
https://hamatti.org/posts/blaugust-2023-lessons-learned/
<h2 class="notion-heading_2 notion-color-default">
<b class="notion-text-bold">I completed Blaugust 2023!</b>
</h2>
<p>
I’m a very proud owner of a Diamond Rainbow Award for publishing 31 posts in
31 days during Blaugust 2023. It was quite a journey and like all challenges
like these, I’m kinda happy it’s over.
</p>
<p>
Over the years, my blogging has been sporadic at best. In 2013, I wrote a lot,
then there were years when it was a post here and another there. 2018 and 2019
I started to get more serious about writing and tried to publish weekly with
varying results. Starting with 2020, I’ve been able to publish a blog post
almost every Wednesday.
</p>
<p>
After I reached my first full year of weekly blogging, I did
<a href="https://hamatti.org/posts/over-a-year-of-weekly-blogging/" class="notion-text-href">a reflection post</a>
of that year.
</p>
<p>
Publishing a blog post once a week is a challenge enough, especially when I
want to have a good amount of technical blog posts with tested example code
and that requires research and coding and testing.
</p>
<p>Publishing a blog post every day is another story.</p>
<h2 class="notion-heading_2 notion-color-default">
Every day is a lot of work
</h2>
<p>
First learning is kinda the obvious I feel. Maintaining a daily schedule
turned out to be a bit easier than I first imagined but also a lot of work. I
did not <i class="notion-text-italic">write</i> every day but I did
<i class="notion-text-italic">publish</i> every day. Writing into the buffer
when I have time and then publishing on a given interval is the solution I’ve
learned over the years as the secret sauce to keeping up with any schedule.
</p>
<p>
The
<a href="https://hamatti.org/posts/website-rewrite-and-switching-to-notion-as-cms/" class="notion-text-href">website rewrite work</a>
that I did was a bit wild idea to do during this kind of month but it ended up
helping a lot also because it gave me an extra boost to write as I enjoyed the
new system more than the previous one.
</p>
<h2 class="notion-heading_2 notion-color-default">
Putting in the work rather than waiting for inspiration
</h2>
<p>
A major learning from this month was that I was able to do it in the first
place. Around the end of the second week, I started to notice that I got
better and better at just sitting down and starting to write.
</p>
<p>
I’ve always been an inspiration writer to the extent that usually at least
once a week, opportunity and inspiration met and I managed to get a post done.
This made writing for work sometimes challenging but after this month, I’m way
more confident and comfortable writing when needed.
</p>
<p>
That gives me a lot of hope as I dream of one day being able to write for a
living. Probably not as a full-time job but as a part of it.
</p>
<h2 class="notion-heading_2 notion-color-default">Blaugust community</h2>
<p>
I really enjoyed being part of a blogging community for this month. I’ve been
mostly writing in isolation and only occasionally chatting with other writers.
The Blaugust Discord has been a great source of inspiration, good discussions
and tips for writing. I also got a few friends to join and we’ve had daily
discussions and even some blog writing coffee sessions which I’ve enjoyed a
ton.
</p>
<p>
That’s the part I wish to keep and even do more in the future. I’ve been
thinking about starting something similar to
<a href="https://shutupwrite.com/" class="notion-text-href">Shut Up & Write</a>
community but I’m trying to avoid becoming a lead in new communities and
rather focus on the existing ones.
</p>
<h2 class="notion-heading_2 notion-color-default">
A weird feeling of cheating
</h2>
<p>
Throughout the month, I had this weird feeling that I was “cheating” with my
blog posts to meet the requirements of the challenge. Especially when I was
publishing my 5-post miniseries on projects I’m proud of or tech bloggers I
read. They were blog posts I might not have written, especially all 5 of each
if it wasn’t for Blaugust and I guess that’s what got my inner critic so mad.
</p>
<p>
And it’s weird because those posts lead into great discussions and other
people finding interesting blogs to read so they achieved their most important
job of a blog post.
</p>
<p>
Towards the end, I got really satisfied with my writing though. The website
rewrite project with its accompanying projects of dark mode and cookie consent
were great and the Steam Deck post turned out really good as well. Likewise
for the Python TDD post.
</p>
<p>
So I aim to not give my inner critic’s opinions any weight even though it
keeps screaming to my ear.
</p>
<h2 class="notion-heading_2 notion-color-default">Return to weekly pace</h2>
<p>
With all that being said, I’m going to return to my previous weekly schedule
starting in September. I decided to do that for a couple of reasons:
</p>
<p>
First, maintaing a daily pace for long, with the style of blog posts I like to
write is not going to be sustainable and I’d just burn out eventually. Given
I’ve occasionally failed to reach my weekly pace, keeping up with daily just
won’t happen.
</p>
<p>
Second, even if I could keep up with daily publishing, it would happen at the
expense of me being able to write more in-depth technical stories. I’m looking
to write more technical stuff again in September. And December is right around
the corner and that means Advent of Code will take all my extra time.
</p>
<p>
I’m really happy that the 31st of August lands on Thursday this year and not
for example on Tuesday. I get almost a week off from writing which I will
cherish and then return to blogging on the first Wednesday of September with
new fresh ideas.
</p>
<h2 class="notion-heading_2 notion-color-default">My goals for Blaugust</h2>
<p>
In
<a href="https://hamatti.org/posts/blaugust-2023-here-we-come/" class="notion-text-href">the introduction post on the 1st</a>, I wrote
</p>
<p>My personal goals for Blaugust for this first year are:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">Write more</li>
<li class="notion-bulleted_list_item notion-color-default">
Experiment with different type of content
</li>
<li class="notion-bulleted_list_item notion-color-default">
Learn from others and get inspired by the community
</li>
<li class="notion-bulleted_list_item notion-color-default">
Help someone get started or restarted with blogging
</li>
</ul>
<p>
One thing I want to do more in the future is to have a discussion with other
blogs. Right now, most of what I write is kinda in isolation on this blog. But
I read a lot of other blogs and many bring up nice topics that I’d like to
start “replying” to or further taking the discussion.
</p>
<p>
I’ve learned a lot from others and gained a ton of new inspiration to writing.
And I’ve gotten at least two people to write more through participation to
Blaugust which makes me so so happy.
</p>
<p>Tally up the scores:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
I published
<a href="https://hamatti.org/blog/blaugust-2023/" class="notion-text-href">31 blog posts on this blog</a>, one each day of August
</li>
<li class="notion-bulleted_list_item notion-color-default">
I published
<a href="https://www.syntaxerror.tech/syntax-error-6-debugging-css/" class="notion-text-href">a Syntax Error newsletter issue</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
I wrote a long-form wiki entry about a in-group community lore to one of my
community’s wiki
</li>
<li class="notion-bulleted_list_item notion-color-default">
I rewrote the entire
<a href="http://hamatti.org/" class="notion-text-href">hamatti.org</a>
website
</li>
</ul>
<p></p>
Use pangrams to avoid mistakes in conference badge prints
2023-08-30T00:00:00Z
https://hamatti.org/posts/use-pangrams-to-avoid-mistakes-in-conference-badge-prints/
<p>
<b class="notion-text-bold">The quick brown fox jumps over the lazy dog.</b>
</p>
<p>
If you’ve ever worked with computers or fonts, I bet you’ve seen that
seemingly nonsense sentence multiple times in your life. It’s an example of a
<a href="https://en.wikipedia.org/wiki/Pangram" class="notion-text-href">pangram</a>
– a sentence that contains all the letters of the alphabet in given language.
I’ve wanted to write about pangrams for a long time.
</p>
<h2 class="notion-heading_2 notion-color-default">
Character errors in conference badges
</h2>
<p>
I’ve organized a few (hundred) events during my lifetime and I’ll admit,
things haven’t always gone smoothly when it comes to printing name badges of
partipants.
</p>
<p>
This is especially bad when it’s about people’s names: many of us are very
emotionally connected to our names and not taking the moment to guarantee they
are correct can feel understandably bad.
</p>
<p>
Like this badge of Adrià Mercader
<a href="https://www.tumblr.com/canthandlemyname" class="notion-text-href">who is no stranger to having his name written wrong</a>.
</p>
<img src="https://hamatti.org/assets/img/posts/use-pangrams-to-avoid-mistakes-in-conference-badge-prints/1.png" alt="A conference badge of csv, conf 2016 for Adrià Mercader with first name written as “Adri” followed by two question marks in black triangles. " />
<p>
The reason this often happens, is that organizers pick a font that looks nice
and then sends the list of names that come directly from visitors to the
printer and with non-ascii characters, mistakes can happen.
</p>
<h2 class="notion-heading_2 notion-color-default">
Make pangrams a part of your design system or brand guidelines
</h2>
<p>
When you are working your events brand guidelines or building a design system
for your website and prints, consider adding multiple pangrams in various
languages to your system, displayed in the font you are using.
</p>
<p>
That’s what the fox is doing when it’s jumping over that dog: it’s helping you
see on a glance what any given font looks like for every letter. It’s very
popular but it only deals with letters a to z without any diacritics or other
letters available in other languages.
</p>
<p>
Luckily,
<a href="https://clagnut.com/blog/2380/" class="notion-text-href">Richard Rutter has saved an extensive list of pangrams</a>
that was deleted from Wikipedia.
</p>
<p>
For example, if you want to make sure your Finnish visitors have their names
written correctly, you can add<i class="notion-text-italic">
“Charles Darwin jammaili åken hevixylofonilla Qatarin yöpub Zeligissä.” </i>to your design system to see if <i class="notion-text-italic">ä</i>,
<i class="notion-text-italic">ö</i> and
<i class="notion-text-italic">å</i> are displayed correctly. To help your
Czech friends avoid awkward question marks in their names, you can add
<i class="notion-text-italic">Nechť již hříšné saxofony ďáblů rozezvučí síň úděsnými tóny waltzu, tanga a
quickstepu. </i>And the list goes on.
</p>
<p>
Having these in a single page of your design system or brand guidebook helps
spot irregularities and missing characters in fonts. I strongly believe it’s
not just about if the character will print (some fonts or systems may use
fallback fonts) but it must print in equal style to others. When fallback is
used, the font weights or styles don’t often match and it becomes obvious that
the original, beautiful and carefully selected font wasn’t inclusive of all
participants’ names.
</p>
<p>
And finally, do a quick run of all the actual names using your desired font
once you have the name list before sending them to print – and be willing to
adapt the font if any names show up misprinted.
</p>
<p>
Getting your badge with your name printed wrong is a guarantee that the
participant will remember your conference – but their first impressions won’t
be great.
</p>
<h2 class="notion-heading_2 notion-color-default">
Basic fonts often do well, custom ones start to show issues
</h2>
<p>
Basic fonts like Helvetica do usually very well, here’s an example of that:
</p>
<img src="https://hamatti.org/assets/img/posts/use-pangrams-to-avoid-mistakes-in-conference-badge-prints/2.png" alt="A list of pangrams in different languages in Helvetica, all working fine " />
<p>
Other fonts like hand-writing style Sedgwick Ave Display is surprisingly good
but lacks cyrillic for example:
</p>
<img src="https://hamatti.org/assets/img/posts/use-pangrams-to-avoid-mistakes-in-conference-badge-prints/3.png" alt="A list of pangrams in different languages in Sedgwick Ave Display, mostly working fine except for cyrillic " />
<p>
And finally fonts like Tulpen One show the issue of not every character being
made the same
</p>
<img src="https://hamatti.org/assets/img/posts/use-pangrams-to-avoid-mistakes-in-conference-badge-prints/4.png" alt="A list of pangrams in different languages in Sedgwick Ave Display, huge variance in letters even within an alphabet " />
<p>
Given that you can control most of the things written on your materials like
website and print, you can use almost any hand picked font for those. But for
your badge name font, you can’t control what characters people have in their
names so you should be prepared with a font that works with every name.
</p>
My First 100K
2023-08-29T00:00:00Z
https://hamatti.org/posts/my-first-100k/
<p>
I have a difficult relationship with metrics for website or social media. I’m
constantly exposed to the barrage of advice and posts about metrics and
analytics and growth and all that through social media and I can’t lie, it has
had a negative effect on me.
</p>
<p>
I don’t want to care about them because most of the things people do to chase
the growth and high numbers are actually negative things to the readers (and
only there to appease The Algorithm). I want to write in my style and every
time truly prioritise the reader over any kind of recommendation algorithm. My
blog is also what they call “variety blog” these days as I don’t have a niche
focus on specific technology or part of life but as the regular readers have
learned to know, I write about all sorts of things.
</p>
<p>
It’s been hard to convince myself that I can’t get all the wins if I don’t
make those sacrificies so I should just be happy with each individual person
who learns something new or gets inspired to do something new in their life
through reading my blog.
</p>
<p>
And as I don’t sell anything, I have no ads and nothing like that, it’s not
like the growth would bring an immediate financial reward or anything - which
is also a perfectly fine goal for a blog to exist. My main motivations are to
get the thoughts and experiences from my head to a form that others can read
and learn from, to document technical topics so I can link to them if ever
needed and to build a body of work that I can use when applying for jobs or
gigs.
</p>
<p>
But I can’t lie, I was ecstatic when I noticed that for the first time ever,
my blog had reached over 100K views in a 30-day window. For years, I used to
say that my mom is the only one who reads my blog but I have to admit, that’s
no longer the case.
</p>
<img src="https://hamatti.org/assets/img/posts/my-first-100k/1.png" alt="A pink Muumi Mehuli soda can and a pink donut. " />
<p>
To celebrate, I got my favorite
<a href="https://arnolds.fi/en/sweet/pomada-creme/" class="notion-text-href">pomada creme donut</a>
and a Moomin raspberry soda.
</p>
<p>
The growth has been very slow and steady over the years but then this summer
it absolutely blew up and I started gaining exponential growth around mid to
late July.
</p>
<p>
I don’t have detailed analytics because I don’t want to track my users but
through Netlify’s server-side analytics, I can see the monthly numbers of
views and visitors and the top performing pages.
</p>
<p>
It’s interesting to see how these numbers will evolve over the next 6 months:
have I been able to capture the new audience as repeat readers and how much
effect will it have when I switch back to weekly blogging in September.
</p>
<p>
(The title of this blog post is a homage to
<a href="https://herfirst100k.com/" class="notion-text-href">Tori Dunlap’s great financial book Her First 100K</a>)
</p>
Custom cookie consent for video embeds
2023-08-28T00:00:00Z
https://hamatti.org/posts/custom-cookie-consent-for-video-embeds/
<p>
Embedding media into a website comes with an annoyance as those embeds set
cookies and track your readers. But sometimes it can be worth it, so I wanted
to add a way to deal with embeds, starting with Youtube, on my blog in a way
that doesn’t involve annoying cookie consent popups and gives the reader full
control over each video.
</p>
<h2 class="notion-heading_2 notion-color-default">
What does this look like for the end user?
</h2>
<p>
Let’s say I want to add
<a href="https://www.youtube.com/watch?v=GZGY0wPAnus" class="notion-text-href">Apollo Robbins’ great TED Talk about the art of misdirection</a>
to my blog. First option is to do what I just did here: link to it and tell
readers to go watch it.
</p>
<p>
But if I want to let people watch it while keeping the blog post open, I can
embed it into my blog post in Notion and when it gets rendered in my site, it
looks like this:
</p>
<img src="https://hamatti.org/assets/img/posts/custom-cookie-consent-for-video-embeds/1.png" alt="Screenshot of this blog showing article Youtube embed example with a video thumbnail of Apollo Robbins’ TED talk and a play button on top of it " />
<p>It will show the thumbnail of the video with a Play button.</p>
<p>
If you click anywhere on the picture, it will show a cookie consent form in
place of the video:
</p>
<img src="https://hamatti.org/assets/img/posts/custom-cookie-consent-for-video-embeds/2.png" alt="Screenshot of cookie consent form when clicking Play on a video embed, informing the user that Youtube sets cookies for embedded videos and offering options to accept, decline or watch in Youtube. " />
<p>
The reader has the option to Accept, in which case the video will be embedded
on the page, Youtube sets its own cookies and you can watch the video. If they
decline or close from the <code class="notion-text-code">x</code> button, it
will got back to the previous view and if they click Watch on Youtube, it will
open the video in Youtube on a new tab.
</p>
<img src="https://hamatti.org/assets/img/posts/custom-cookie-consent-for-video-embeds/3.png" alt="Screenshot of Youtube video The art of misdirection by Apollo Robbins embedded and playing in the blog " />
<p>
You can test it out yourself with this actual embed of this video (and if
don’t like Youtube, you can watch this
<a href="https://www.ted.com/talks/apollo_robbins_the_art_of_misdirection" class="notion-text-href">great and entertaining talk in TED.com</a>).
</p>
<div class="embed-container" data-url="https://www.youtube.com/watch?v=GZGY0wPAnus">
<button class="youtube-thumbnail" aria-label="Play Youtube video undefined">
<img src="https://hamatti.org/assets/img/posts/custom-cookie-consent-for-video-embeds/GZGY0wPAnus.jpg" alt="Youtube thumbnail for undefined" />
<img src="https://hamatti.org/assets/img/play-button.png" class="play-button" alt="Play" />
</button>
<div class="consent-form hide">
<p class="consent-header">
<strong>Consent for 3rd party cookies</strong><button class="close-consent" aria-label="Close consent form">x</button>
</p>
<p>
<a href="https://policies.google.com/technologies/cookies" target="_blank">Youtube sets cookies</a>
when you watch embedded videos. By clicking <strong>Accept</strong>, you
consent to these cookies being set. Alternatively, you can click
<strong>Watch on Youtube</strong> to watch it there or
<strong>Decline</strong> to prevent video from being shown.
</p>
<div class="consent-nav">
<button class="consent-confirm">Accept</button>
<button class="close-consent">Decline</button>
<a href="https://www.youtube.com/watch?v=GZGY0wPAnus" target="_blank">Watch on Youtube</a>
</div>
</div>
</div>
<h3 class="notion-heading_3 notion-color-default">
How about Youtube’s nocookie embeds?
</h3>
<p>
Youtube offers an option to use
<code class="notion-text-code"><a href="http://youtube-nocookie.com/embed/[ID]" class="notion-text-href">youtube-nocookie.com/embed/[ID]</a></code>
URL in the embed but it doesn’t actually block all cookies. What it apparently
does it that it just doesn’t connect the video with a logged in Youtube
account.
</p>
<p>
<a href="https://cloudfour.com/thinks/youtube-no-cookies-adds-cookies/" class="notion-text-href">Jason Grigsby has written about it</a>
as well as
<a href="https://axbom.com/embed-youtube-videos-without-cookies/" class="notion-text-href">Per Axbom</a>, if you want to know more about the details. If you are relying on the
nocookie option because of its deceptive name, you should consider alternative
options to offer your readers better ways.
</p>
<h2 class="notion-heading_2 notion-color-default">How is it built?</h2>
<h3 class="notion-heading_3 notion-color-default">Pre-build rendering</h3>
<p>
When I add a link to Youtube on a new line in my blog post in Notion, Notion
asks if I want to do a Youtube embed. An embed gets stored in a
<code class="notion-text-code">video</code> block in Notion’s data model.
</p>
<p>So I started by creating a custom renderer for video blocks:</p>
<pre class="language-javascript"><code class="language-javascript">const embedRenderer = createBlockRenderer("video", async (data, renderer) => {
const URL = data.video.external.url;
if (URL.includes("youtube")) {
let description = data.video.caption.plain_text;
let videoId = URL.split("=")[1];
let imageName = `${videoId}.jpg`;
let imageUrl = `https://i3.ytimg.com/vi/${videoId}/hqdefault.jpg`;
// Custom housekeeping relevant to my blog omitted
return `<div class="embed-container" data-url="${URL}">
<button class="youtube-thumbnail" aria-label="Play Youtube video ${description}">
<img src="${imageFilePath}" alt="Youtube thumbnail for ${description}" />
<img src="/assets/img/play-button.png" class="play-button" alt="Play">
</button>
<div class="consent-form hide">
<p class="consent-header"><strong>Consent for 3rd party cookies</strong><button class="close-consent" aria-label="Close consent form">x</button></p>
<p><a href="https://policies.google.com/technologies/cookies" target=_blank>Youtube sets cookies</a> when you watch embedded videos. By clicking <strong>Accept</strong>, you consent to these cookies being set. Alternatively, you can click <strong>Watch on Youtube</strong> to watch it there or <strong>Decline</strong> to prevent video from being shown.</p>
<div class="consent-nav">
<button class="consent-confirm">Accept</button>
<button class="close-consent">Decline</button>
<a href="${URL}" target=_blank>Watch on Youtube</a>
</div>
</div>
</div> `;
} else {
console.error(`Support for ${URL} is not yet added.`);
}
});</code></pre>
<p>There are a couple of things that happen here:</p>
<p>
First, I check if the URL has youtube in it and if not, I print an error to
the console reminding myself that I’m embedding something that I haven’t build
support for yet. Since I’m the solo creator and developer in this project, I
can get away with a bit of a shortcut like this. For any system that would
have different people writing blog posts, I’d make it way more robust. But
since I know when I’m adding a new type of embed, it’s just there for a
reminder and a safeguard that nothing wonky goes into my blog.
</p>
<p>Second, I get the thumbnail image so I can show it to the user.</p>
<p>
Finally, I create a HTML string that has a few things. It has the URL stored
as a data-attribute (so we’re not sending any requests to Youtube on page
load) and have a play button that gets positioned on top of the image to
signify to users that it is a video that can be clicked.
</p>
<p>
I don’t have access to the actual Youtube video data though so I’m using the
same trick I do with images: I store the video name in the caption of the
embed and extract it from there.
</p>
<p>
I tell the user what this cookie consent form is there for and give them three
options: Accept the cookies and embed video, decline and not embed the video
or open the Youtube video in
<a href="http://youtube.com/" class="notion-text-href">youtube.com</a> on a
new tab.
</p>
<p>
This rendering happens on pre-build time when I download my blog into my local
Eleventy project.
</p>
<h3 class="notion-heading_3 notion-color-default">Client runtime</h3>
<p>
When the user reaches my blog and decides to watch a video, I run some
Javascript to show the consent form and deal with it accordingly.
</p>
<pre class="language-javascript"><code class="language-javascript">let embedTemplate = `<iframe
width="640"
height="480"
src="[url]"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>`;
Object.values(document.querySelectorAll(".youtube-thumbnail")).forEach(
(embed) => {
embed.addEventListener("click", (ev) => {
ev.preventDefault();
let container = ev.target.parentElement.parentElement;
let image = container.children[0];
let form = container.children[1];
image.classList.add("hide");
form.classList.remove("hide");
Object.values(
container.querySelectorAll(":scope .close-consent")
).forEach((btn) =>
btn.addEventListener("click", (ev) => {
const container = ev.target.parentElement.parentElement;
container.classList.add("hide");
image.classList.remove("hide");
})
);
container
.querySelector(":scope .consent-confirm")
.addEventListener("click", (ev) => {
ev.preventDefault();
container.innerHTML = embedTemplate
.replace("[url]", container.dataset.url)
.replace("watch?v=", "embed/");
});
});
}
);</code></pre>
<p>
For each Youtube “faux” embed, I add a click handler that hides the image and
shows the consent form. I then add event listeners to the buttons to accept or
decline the cookies.
</p>
<p>
If the user accepts cookies, I then switch the element’s HTML into a Youtube
embed iframe and replace the
<code class="notion-text-code">[url]</code> placeholder with the one stored in
the parent <code class="notion-text-code">div</code>.
</p>
<p>
At this point, the first network requests fire to youtube.com and Youtube sets
its own cookies.
</p>
<p>
This way, no cookies get set and no requests are sent to Youtube before the
user accepts it.
</p>
<p>
I don’t have a mechanism set to accept all Youtube embed cookies, I figured it
is better for the user to have improved privacy rather than a slightly
improved user experience. And I don’t plan to have a lot of embedded videos at
any given post so user might very rarely actually run into this.
</p>
<h2 class="notion-heading_2 notion-color-default">Wrap up</h2>
<p>
I’m happy to have this first iteration done. It will likely improve over time
as I come up with better ideas and see it in action a bit more.
</p>
<p>
Big thanks to
<a href="https://www.oh-no.ooo/" class="notion-text-href">Lucia</a> for the
inspiration and sparring on this during our blog writing coffee session!
</p>
Compressing overlapping strings in Python
2023-08-27T00:00:00Z
https://hamatti.org/posts/compressing-overlapping-strings-in-python/
<p>A friend published a challenge in one of our Discord groups:</p>
<h2 class="notion-heading_2 notion-color-default">The Challenge</h2>
<p>
<b class="notion-text-bold">Goal:</b>
</p>
<p>
Given a list of N strings, your task is to create a new string that
incorporates all the strings from the list. The resultant string should be the
shortest possible by taking advantage of overlapping sections between the
strings.
</p>
<p>
<b class="notion-text-bold">Guidelines:</b>
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
The resulting string should contain all the original strings entirely.
</li>
<li class="notion-bulleted_list_item notion-color-default">
The strings from the list can appear in any order in the resulting string.
</li>
<li class="notion-bulleted_list_item notion-color-default">
If multiple shortest strings are possible, returning any one of them is
acceptable.
</li>
<li class="notion-bulleted_list_item notion-color-default">
If there's no possible overlap among the strings, the result will simply be
a concatenation of all the strings in the list.
</li>
<li class="notion-bulleted_list_item notion-color-default">
All strings in the input list are unique.
</li>
</ul>
<p>
A list of N strings, where each string has a length between 1 and 100
inclusive.
</p>
<p>
<b class="notion-text-bold">Output:</b>
</p>
<p>
A single string that represents the most compressed combination of the given
strings.
</p>
<p>
<b class="notion-text-bold">Example:</b>
</p>
<p>Given the input list:</p>
<pre class="language-plain text"><code class="language-plain text">strings = ["pleasure", "apple", "rendered", "clap", "suren"]</code></pre>
<p>A possible output is:</p>
<pre class="language-plain text"><code class="language-plain text">"clappleasurendered"</code></pre>
<p>
I decided to give it a goal since it’s still almost 100 days until December
and
<a href="https://adventofcode.com/" class="notion-text-href">Advent of Code</a>.
</p>
<img src="https://hamatti.org/assets/img/posts/compressing-overlapping-strings-in-python/1.png" alt=" " />
<h2 class="notion-heading_2 notion-color-default">Let’s tackle it!</h2>
<p>
To start with, I want to be able to define an overlap between two words.
Python’s
<a href="https://docs.python.org/3/library/collections.html#collections.namedtuple" class="notion-text-href">namedtuples</a>
are great for defining data structures that are easier to understand than
regular tuples and more lightweight than objects.
</p>
<h3 class="notion-heading_3 notion-color-default">Setting things up</h3>
<pre class="language-python"><code class="language-python">from collections import namedtuple
Overlap = namedtuple('Overlap', ['start', 'end', 'length'])</code></pre>
<p>
<code class="notion-text-code">Overlap</code> is a tuple that has two words
(start and end) and length of the overlap. If you’ve ever followed my
solutions during Advent of Code, you know how I love to use them for puzzles
like these all the time. Tuples are immutable and namedtuples allow us to
document what they are and what each item means so it’s a win-win.
</p>
<h3 class="notion-heading_3 notion-color-default">
Finding overlap between two words
</h3>
<p>
To find an overlap between two words, I decided to go with a TDD approach and
start with two words that don’t overlap:
</p>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
"""
return None
if __name__ == '__main__':
import doctest
doctest.testmod()</code></pre>
<p>
All tests green! Our function matches its first spec and returns
<code class="notion-text-code">None</code> if there’s no overlap.
</p>
<p>
Next, I want to add a case that checks if the end of the first word overlaps
with the beginning of the second word:
</p>
<pre class="language-plain text"><code class="language-plain text">Failed example:
find_overlap('apple', 'pleasure')
Expected:
Overlap(start='apple', end='pleasure', length=3)
Got nothing</code></pre>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds an overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
>>> find_overlap('apple', 'pleasure')
Overlap(start='apple', end='pleasure', length=3)
"""
max_overlap = min(len(first), len(second))
for i in range(1, max_overlap):
if first[-i:] == second[:i]:
return Overlap(start=first, end=second, length=i)
return None</code></pre>
<p>
First I added a new test, got a failure and then implemented it to pass! 2
specs now work.
</p>
<p>
Next, I’ll add the mirror case of previous: second word’s end overlaps with
the first one’s start.
</p>
<pre class="language-plain text"><code class="language-plain text">Failed example:
find_overlap('erratic', 'player')
Expected:
Overlap(start='player', end='erratic', length=2)
Got nothing</code></pre>
<p>Let’s write code that makes it pass!</p>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds an overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
>>> find_overlap('apple', 'pleasure')
Overlap(start='apple', end='pleasure', length=3)
>>> find_overlap('erratic', 'player')
Overlap(start='player', end='erratic', length=2)
"""
max_overlap = min(len(first), len(second))
for i in range(1, max_overlap):
if first[-i:] == second[:i]:
return Overlap(start=first, end=second, length=i)
for i in range(1, max_overlap):
if second[-i:] == first[:i]:
return Overlap(start=second, end=first, length=i)
return None</code></pre>
<p>We loop over again, this time turning them around and the tests pass!</p>
<p>
However, we’ve now introduced an extra
<code class="notion-text-code">for</code> loop that doubles the time of our
worst case.
</p>
<p>
Since we have passing tests, we can refactor this to take away the second
<code class="notion-text-code">for</code> loop:
</p>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds an overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
>>> find_overlap('apple', 'pleasure')
Overlap(start='apple', end='pleasure', length=3)
>>> find_overlap('erratic', 'player')
Overlap(start='player', end='erratic', length=2)
"""
max_overlap = min(len(first), len(second))
for i in range(1, max_overlap):
if first[-i:] == second[:i]:
return Overlap(start=first, end=second, length=i)
if second[-i:] == first[:i]:
return Overlap(start=second, end=first, length=i)
return None</code></pre>
<p>
We now check for both options on each iteration, making the code much cleaner
and reducing two for loops into one.
</p>
<p>This works for words where the overlap is not symmetric.</p>
<p>However, adding another test case shows our fault:</p>
<pre class="language-python"><code class="language-python">>>> find_overlap('payee', 'eerily')
Overlap(start='payee', end='eerily', length=2)</code></pre>
<pre class="language-plain text"><code class="language-plain text">Failed example:
find_overlap('payee', 'eerily')
Expected:
Overlap(start='payee', end='eerily', length=2)
Got:
Overlap(start='payee', end='eerily', length=1)</code></pre>
<p>Our matching case was too eager and returned as soon as it found a match.</p>
<p>
To fix this, I needed to make it go through all the options and return the end
result:
</p>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds an overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
>>> find_overlap('apple', 'pleasure')
Overlap(start='apple', end='pleasure', length=3)
>>> find_overlap('erratic', 'player')
Overlap(start='player', end='erratic', length=2)
>>> find_overlap('payee', 'eerily')
Overlap(start='payee', end='eerily', length=2)
"""
max_overlap = min(len(first), len(second))
overlap = None
for i in range(1, max_overlap):
if first[-i:] == second[:i]:
overlap = Overlap(start=first, end=second, length=i)
if second[-i:] == first[:i]:
overlap = Overlap(start=second, end=first, length=i)
return overlap</code></pre>
<p>
Now our tests are green again but we do a lot of extra work as if we have two
10-character words that overlap for 2 characters, we still match it through
for all variations between lengths of 1 and 10 – twice.
</p>
<p>
To tackle this, I added an extra check: if we had an overlap at some point and
now we don’t anymore either direction, return the overlap:
</p>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds an overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
>>> find_overlap('apple', 'pleasure')
Overlap(start='apple', end='pleasure', length=3)
>>> find_overlap('erratic', 'player')
Overlap(start='player', end='erratic', length=2)
>>> find_overlap('payee', 'eerily')
Overlap(start='payee', end='eerily', length=2)
"""
max_overlap = min(len(first), len(second))
overlap = None
for i in range(1, max_overlap):
if first[-i:] == second[:i]:
overlap = Overlap(start=first, end=second, length=i)
elif second[-i:] == first[:i]:
overlap = Overlap(start=second, end=first, length=i)
elif overlap:
return overlap
return overlap</code></pre>
<p>All still green and now we stop as soon as we found the maximum overlap.</p>
<p>
What happens now if we have a special case where there’s overlap both ways?
</p>
<p>Let’s add a test and see!</p>
<pre class="language-python"><code class="language-python">>>> find_overlap('eeriest', 'estimatee')
Overlap(start='eeriest', end='estimatee', length=3)
>>> find_overlap('estimatee', 'eeriest')
Overlap(start='eeriest', end='estimatee', length=3)</code></pre>
<p>
In this test, there’s overlap between the end
<code class="notion-text-code">est</code> of
<code class="notion-text-code">eeriest</code> and start of
<code class="notion-text-code">estimatee</code>, but also another one with the
ending <code class="notion-text-code">ee</code> of
<code class="notion-text-code">estimatee</code> and start of
<code class="notion-text-code">eeriest</code>. And I added a mirror test of
that to make sure our code doesn’t favor first word over the second (which it
does if they are equal length which I’m fine with at this point).
</p>
<h3 class="notion-heading_3 notion-color-default">Time to combine the words</h3>
<p>
Now we have a function that finds overlaps and I’m quite happy with it. Let’s
start looking at the next part of the challenge:
</p>
<blockquote class="notion-quote notion-color-default">
create a new string that incorporates all the strings from the list
</blockquote>
<pre class="language-python"><code class="language-python">def combine_words(words):
"""
Compresses words based on overlaps.
>>> combine_words(['magnificent', 'challenge'])
'magnificentchallenge'
"""
return ''.join(words)</code></pre>
<p>
The first case of this function is two words that don’t overlap and they just
get combined in either direction.
</p>
<p>Next case, we want to see if we can combine two words that overlap:</p>
<pre class="language-python"><code class="language-python">Failed example:
combine_words(['apple', 'pleasure'])
Expected:
'appleasure'
Got:
'applepleasure'</code></pre>
<p>
To make this pass, we check if there is an overlap and if is, combine words:
</p>
<pre class="language-python"><code class="language-python">def combine_words(words):
"""
Compresses words based on overlaps.
>>> combine_words(['magnificent', 'challenge'])
'magnificentchallenge'
>>> combine_words(['apple', 'pleasure'])
'appleasure'
"""
if overlap := find_overlap(*words):
return f'{overlap.start}{overlap.end[overlap.length:]}'
return ''.join(words)</code></pre>
<p>And we are green again! But this only works with two words.</p>
<p>A quick Python primer if there’s some unfamiliar parts in the above code:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">overlap := find_overlap(*words)</code> uses
<a href="https://peps.python.org/pep-0572/" class="notion-text-href">a walrus operator</a>
(<code class="notion-text-code">:=</code>) that enables us to make
assignments in <code class="notion-text-code">if</code> or
<code class="notion-text-code">while</code> clauses and match against that
result.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">find_overlap(*words)</code> uses
<code class="notion-text-code">*words</code> that is
<a href="https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments" class="notion-text-href">argument unpacking</a>
and it expands a list into separate arguments for the function
</li>
<li class="notion-bulleted_list_item notion-color-default">
<code class="notion-text-code">return f'{overlap.start}{overlap.end[overlap.length:]}'</code>
uses
<a href="https://peps.python.org/pep-0498/" class="notion-text-href">f-strings</a>
where everything inside curly braces (<code class="notion-text-code">{ .. }</code>) gets evaluated as Python
</li>
</ul>
<pre class="language-plain text"><code class="language-plain text">Failed example:
combine_words(['apple', 'pleasure', 'surely'])
Exception raised:
Traceback (most recent call last):
File "<doctest __main__.combine_words[2]>", line 1, in <module>
combine_words(['apple', 'pleasure', 'surely'])
File "/string-compression-challenge/tdd.py", line 43, in combine_words
if overlap := find_overlap(*words):
TypeError: find_overlap() takes 2 positional arguments but 3 were given</code></pre>
<p>
Now we tried to pass three words into our
<code class="notion-text-code">find_overlap</code> function which breaks
because it only accepts two. Time to refactor!
</p>
<p>
This time, the complexity of the code and challenge increased a lot and I had
to remind myself that when I have a failing test, my only goal is to make that
test pass. I can write inefficient code, I can use extra variables, I can name
them badly. This is not the moment when writing
<i class="notion-text-italic">good code</i> is the goal.
</p>
<p>
To get there, I made a modification to the
<code class="notion-text-code">find_overlap</code> function so it does not
return anymore <code class="notion-text-code">None</code> but an
<code class="notion-text-code">Overlap</code> with
<code class="notion-text-code">length</code> of 0:
</p>
<pre class="language-python"><code class="language-python"># replace return None with
return Overlap(start=first, end=second, length=0)</code></pre>
<p>
With this change, I was able to change
<code class="notion-text-code">combine_words</code> to pass our new test:
</p>
<pre class="language-python"><code class="language-python">def combine_words(words):
"""
Compresses words based on overlaps.
>>> combine_words(['magnificent', 'challenge'])
'magnificentchallenge'
>>> combine_words(['apple', 'pleasure'])
'appleasure'
>>> combine_words(['apple', 'pleasure', 'surely'])
'appleasurely'
"""
overlap = find_overlap(*words[:2])
rest = words[2:]
combined = f'{overlap.start}{overlap.end[overlap.length:]}'
while next_word := rest.pop() if rest else False:
overlap = find_overlap(combined, next_word)
combined = f'{overlap.start}{overlap.end[overlap.length:]}'
return combined</code></pre>
<p>
This solution starts by combining the first two words and then tacks the next
word in the list either in the beginning or end of that list based on the
overlap of these two new words until there are no more words in the list.
</p>
<p>Let’s tackle the example case from the challenge:</p>
<pre class="language-plain text"><code class="language-plain text">Failed example:
combine_words(['pleasure', 'apple', 'rendered', 'clap', 'suren'])
Expected:
'clappleasurendered'
Got:
'clappleasuresurendered'</code></pre>
<p>
Our current solution relies on the ordering of the words and does not find the
most optimal solution. Let’s make the test green!
</p>
<p>
As I was working on this, I discovered a bug in our
<code class="notion-text-code">find_overlap</code>:
</p>
<pre class="language-plain text"><code class="language-plain text">Failed example:
find_overlap('sure', 'pleasure')
Expected:
Overlap(start='pleasure', end='sure', length=4)
Got:
Overlap(start='sure', end='pleasure', length=0)</code></pre>
<p>
Before I could continue on the
<code class="notion-text-code">combine_words</code>, I needed to fix this case
where the entire word could be part of an overlap.
</p>
<pre class="language-python"><code class="language-python">def find_overlap(first, second):
"""
Finds an overlapping substrings between two words
>>> find_overlap('magnificent', 'challenge')
Overlap(start='magnificent', end='challenge', length=0)
>>> find_overlap('apple', 'pleasure')
Overlap(start='apple', end='pleasure', length=3)
>>> find_overlap('erratic', 'player')
Overlap(start='player', end='erratic', length=2)
>>> find_overlap('payee', 'eerily')
Overlap(start='payee', end='eerily', length=2)
>>> find_overlap('eeriest', 'estimatee')
Overlap(start='eeriest', end='estimatee', length=3)
>>> find_overlap('estimatee', 'eeriest')
Overlap(start='eeriest', end='estimatee', length=3)
>>> find_overlap('suren', 'pleasure')
Overlap(start='pleasure', end='suren', length=4)
>>> find_overlap('sure', 'pleasure')
Overlap(start='pleasure', end='sure', length=4)
"""
max_overlap = min(len(first), len(second))
overlap = None
for i in range(0, max_overlap+1):
if first[-i:] == second[:i]:
overlap = Overlap(start=first, end=second, length=i)
elif second[-i:] == first[:i]:
overlap = Overlap(start=second, end=first, length=i)
elif overlap:
return overlap
if overlap:
return overlap
return Overlap(start=first, end=second, length=0)</code></pre>
<p>
I fixed two things here: one was the indices (from
<code class="notion-text-code">1, max_overlap</code> to
<code class="notion-text-code">0, max_overlap+1</code>to make sure we catch
entire words. This one was left because of my original attempt to avoid
matching just one letter eagerly (but since we fixed that earlier, it needed
to be changed here).
</p>
<p>
Second, if the entire shorter word was looped over and there was an overlap,
the code never ended into the
<code class="notion-text-code">elif overlap</code> in the loop and needed
another one.
</p>
<p>Now I’m bit more confident that function works.</p>
<pre class="language-python"><code class="language-python">def combine_overlap(overlap):
"""
>>> combine_overlap(Overlap(start='clap', end='apple', length=2))
'clapple'
"""
return f'{overlap.start}{overlap.end[overlap.length:]}'
def combine_words(words):
"""
Compresses words based on overlaps.
>>> combine_words(['magnificent', 'challenge'])
'challengemagnificent'
>>> combine_words(['apple', 'pleasure'])
'appleasure'
>>> combine_words(['apple', 'pleasure', 'surely'])
'appleasurely'
>>> combine_words(['pleasure', 'apple', 'rendered', 'clap', 'suren'])
'clappleasurendered'
"""
perms = permutations(words)
shortest = None
for permutation in perms:
combined = ''
permutation = list(permutation)
while next_word := permutation.pop() if permutation else False:
overlap = find_overlap(combined, next_word)
combined = combine_overlap(overlap)
if not shortest or len(combined) < len(shortest):
shortest = combined
return shortest</code></pre>
<p>
My next solution that passed the test was to create all the permutations
possible, combine them in each order and find the shortest solution.
</p>
<p>
Tests are green but this is also going through a lot of permutations and with
large number of input words will become very slow. It will require factorial
of input length iterations and factorials grow real fast. With 5 words, it
goes through 120 for loop iterations and with 10 words 3,6 million loop
iterations. And since our list can be up to 100 words, that’s… too many.
</p>
<p>Let’s look at the original challenge to see where we are:</p>
<blockquote class="notion-quote notion-color-default">
<ul>
<li>
The resulting string should contain all the original strings entirely. ✅
</li>
<li>
The strings from the list can appear in any order in the resulting string.
✅
</li>
<li>
If multiple shortest strings are possible, returning any one of them is
acceptable. ✅
</li>
<li>
If there's no possible overlap among the strings, the result will simply
be a concatenation of all the strings in the list. ✅
</li>
<li>All strings in the input list are unique. ✅</li>
</ul>
</blockquote>
<p>
We match all the guidelines and I think we do reach the optimal solution,
given enough time and processing power.
</p>
<h2 class="notion-heading_2 notion-color-default">What’s next?</h2>
<p>
I was hoping to come up with an optimization but so far I haven’t and I
figured this blog post would still be worthy of publishing so I considered a
trick and calling it part 1 so it looks like I planned it up front. This blog
post was less about solving this particular puzzle and more about the process
of arriving at a solution through test-driven development.
</p>
<p>
I might revisit this a bit later if I find a good evening with inspiration to
dive into it.
</p>
Where do my links live?
2023-08-26T00:00:00Z
https://hamatti.org/posts/where-do-my-links-live/
<p>
I woke up this Monday and during my early morning RSS reading session, I found
<a href="https://chriscoyier.net/2023/08/20/how-do-i-save-links-for-later/" class="notion-text-href">Chris Coyier’s How do I save links for later?</a>
and it sparked many thoughts in my head. As someone who also thinks about
these kind of things a lot, I knew I had to write about this too.
</p>
<h2 class="notion-heading_2 notion-color-default">Not too many tabs</h2>
<p>
Originally the discussion sparked somewhat simultaneously in two communities
where we discussed the use of tabs in browsers. A lot of people have
<b class="notion-text-bold">a lot </b>of tabs open all the time and they never
close tabs. That’s their strategy to store things and if the browser would
crash and forget the tabs, they’d lose them all which seems to cause a bit of
anxiety.
</p>
<p>
I personally have never been much of
<i class="notion-text-italic">a tabber. </i>If I have more than a small
handful of tabs open at any given time, I get anxious. I usually keep a few
tabs open as pinned (like mail, calendar) and then very limited amount of tabs
I work actively. I close tabs as soon as I no longer need them actively. I’d
rather open a site over and over again during the day when I need it rather
than keep it open.
</p>
<h2 class="notion-heading_2 notion-color-default">Link storage</h2>
<p>Chris categorized his link storing to three categories:</p>
<blockquote class="notion-quote notion-color-default">
<b class="notion-text-bold">⏰ Short Term </b>📂
<b class="notion-text-bold">Medium Term 🗄️ Long Term</b>
</blockquote>
<p>
and his various approaches to all different ways of keeping and finding the
links.
</p>
<p>I have a bit different categorization myself:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
Links I visit regularly
</li>
<li class="notion-bulleted_list_item notion-color-default">
Links that are “read later”
</li>
<li class="notion-bulleted_list_item notion-color-default">
Links that are “I might need it one day”
</li>
<li class="notion-bulleted_list_item notion-color-default">
Links I want to share with others
</li>
<li class="notion-bulleted_list_item notion-color-default">
Sites I want to follow regularly
</li>
</ul>
<p>
This is mainly what I use browser bookmarks for. I have a bookmarks bar on
Firefox that fits roughly a dozen links or folders. I have a few bookmarklets
that live there (like
<a href="https://indieblog.page/" class="notion-text-href">indie.blog’s random page</a>) and short-term often visited sites like links to sports tournament
schedules when there’s on-going competitions going on (most recently, FIFA
Women’s World Cup and 2023 World Athletics Championships). These I clean up
regularly so when an event ends, I delete the bookmark.
</p>
<p>
I may visit these even a dozen times a day depending on the content. These
also include sites that have regularly updating content but don’t offer a way
to subscribe via RSS or other methods. Often these are also somewhat short
lived, maybe for a season at maximum.
</p>
<p>
If a site has a short, easy to write and remember URL, I don’t bookmark it
because I can access it faster without moving my hands away from the keyboard.
</p>
<h3 class="notion-heading_3 notion-color-default">
Regularly visited links via custom redirects
</h3>
<p>
A “hack” that I started using a while back is mapping complex URLs with custom
redirects through my own site.
</p>
<p>
A good example of this is Turku local transporation system’s bus stop view.
The URL for that looks like
<a href="https://matkamonitori.foli.fi/?stops=164&title=&bg=292938&primary=fefefe&accent=fefefe&logo=fill%3Argb(255%2C255%2C255)&rows=8&disable=&offset=0<=10&langs=fi&lang=fi" class="notion-text-href">https://matkamonitori.foli.fi/?stops=164&title=&bg=292938&primary=fefefe&accent=fefefe&logo=fill:rgb(255,255,255)&rows=8&disable=&offset=0<=10&langs=fi&lang=fi</a>
where the <code class="notion-text-code">stops</code> parameter defines which
stop to show and everything else is styling and filtering and so on.
</p>
<p>
I have mapped that to hamatti.org/foli/[bus_stop] so its easy and quick to
type on mobile, especially thanks to autocomplete. When I reach a stop, I just
check its number (and I remember my most used numbers by heart) from the bus
stop sign and then open this page through custom redirect.
</p>
<h3 class="notion-heading_3 notion-color-default">Read later with Pocket</h3>
<p>
It’s not a coincidence that Pocket used to be called “Read Later App” when it
launched and I’ve been a happy user ever since those days. For me, it’s the
ultimate app for short-term storing links. I have the premium subscription for
it and I use it in a few different ways.
</p>
<p>
First, I use it for literally storing an article so I can read it later. I see
an interesting link on social media or one of the many Slack groups during the
day but know I don’t have time to dig into it at the moment → Save to Pocket.
I’m reading a blog post and something comes up and I need to divert my
attention elsewhere → Save to Pocket. I have a trip coming up with lots of
time without Internet → storing bunch of articles to Pocket for offline
reading.
</p>
<p>
Second, I use it to curate links for my newsletter(s). In
<a href="https://syntaxerror.tech/" class="notion-text-href">Syntax Error</a>
newsletter, I share one or two links to interesting debugging stories from the
web and to make sure I have a good amount of them ready to be published, I
keep them in Pocket and tag them
<code class="notion-text-code">debugging</code>. This way, when the 17th of
the month starts to creep closer, I can open my Pocket, search with the tag
and pick the stories to build my newsletter around.
</p>
<p>
Third, I use it occasionally to transfer a link from one device to another.
Maybe I’m reading something on my iPad or phone and I run into something I
want to store in one of the different ways. I Pocket it and then on my laptop,
I transfer it to some of these other ways to store links.
</p>
<h3 class="notion-heading_3 notion-color-default">Links I might need later</h3>
<p>
This is the sock drawer of links. Something that I
<i class="notion-text-italic">might </i>want to revisit and don’t trust for
the browser history to remember or be easy enough to find.
</p>
<p>
I use a mixture of systems for these. Some I store to the main bookmarks menu
of the browser. Some I store to Notion.
</p>
<p>
This happens less and less these days as I’ve learned I don’t really ever
visit them.
</p>
<h3 class="notion-heading_3 notion-color-default">
Links I want to share with others
</h3>
<p>
I got back in action with
<a href="https://hamatti.org/weeklies" class="notion-text-href">Weeklies</a> a few weeks ago
(although I’m still working out the kinks in my system). During the week,
whenever I read an article that I feel is worth sharing in Weeklies, I log it
to a database table in Notion and then every Sunday, I run a command in my
website project which downloads the new entries and creates a weekly page in
the web page.
</p>
<p>
I often share the best few things I encountered during the week so I often
find myself wanting to reshare them later in discussions and so on. Being able
to find them from my own website whenever that happens is a great luxury.
</p>
<p>
I also share stuff daily in Mastodon but I don’t consider that so much of a
“storing” solution.
</p>
<h3 class="notion-heading_3 notion-color-default">Sites I follow regularly</h3>
<p>
Whenever I find an interesting blog (mostly I read tech blogs), I check if
they have an RSS feed and if they do, I add it to my RSS reader (I use
Feedly). I regularly go through my feed and remove blogs that post regularly
but I find myself not reading them almost ever.
</p>
<p>
If they don’t have RSS, I have a special “Developer websites” folder for
bookmarks where those go with hopes that I’d ever check them again but these
days, that just almost never happens.
</p>
<p>
<a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed/" class="notion-text-href">Providing an RSS feed is so important!</a>
</p>
<p></p>
6 months of recovery, how is life?
2023-08-25T00:00:00Z
https://hamatti.org/posts/6-months-of-recovery-how-is-life/
<p>
<i class="notion-text-italic">Content warning: This post deals with mental health, struggling recovery of
burnout and depressive feelings and thoughts. If you’re not interested in
that or today is not the day you want to read of such things, there will be
a new post coming soon so feel free to skip this and do something better for
your own mental health instead.</i>
</p>
<p>
It’s been 7 months since I let people know
<a href="https://hamatti.org/posts/im-leaving-mozilla/" class="notion-text-href">I’m leaving Mozilla and DevRel</a>
and 5 months since
<a href="https://hamatti.org/posts/a-quick-life-update/" class="notion-text-href">the last update</a>. I really wanted to write an update after a month and then two months and
many times since but life has just been bit too rough for me to put my
thoughts into words.
</p>
<h2 class="notion-heading_2 notion-color-default">How did we get here?</h2>
<p>
I’ve written about it a bit here and bit there but the pandemic and lockdowns
were such a rough blow to me and my physical and mental health. Despite being
lucky to avoid Covid itself, basically losing my ability to do my job and not
being able to come up with a good alternative despite few years of trying did
a bad number on my well-being.
</p>
<p>
Once the regulations lifted and we started to be able to organize events and
stuff again in the spring of 2022, I one day found myself sitting on the floor
of a corridor in our office in the middle of unpacking 300 t-shirts and
hoodies. A colleague asked if everything was okay (as it clearly wasn’t) and I
muttered something along the lines of “Yeah yeah, I’m just taking a breather.”
Realistically, I was so mentally and physically exhausted but I just didn’t
want to believe it myself.
</p>
<p>
During the spring, I went through a few recruitment funnels with an idea to
find a new job to do a complete reset and reboot and start out fresh without
the accumulated mental and social baggage of the pandemic times.
</p>
<p>
When I signed my contract with Mozilla after the interviews, I was all over
the moon. I resigned from my then job, quit before the summer holidays and
moved to Berlin. A new fresh start, I was feeling amazing.
</p>
<p>
As the job started, I think I had a good start. I quickly started meeting
colleagues across the organization to learn their stories and I got to meet
people from the community I was gonna be part of for years to come.
<b class="notion-text-bold">I had finally made it. </b>After years of
worrying, I had finally landed my dream job in a dream company. We had a
fantastic team and I think they enjoyed having me as part of it too.
</p>
<p>
In September
<a href="https://hamatti.org/posts/react-finland-2022-recap/" class="notion-text-href">I visited home to organize React Finland</a>
and in October,
<a href="https://hamatti.org/posts/ad-filtering-dev-summit-2022-recap/" class="notion-text-href">I traveled to Amsterdam</a>
and I remember standing on one of the small bridges, staring towards the
sunset across one of those small canals and thinking: “Life is damn good.”
</p>
<p>
By mid-November, I barely could do anything. I had 50 things I knew I could
and should do as part of my job but I just physically couldn’t do any of them.
Despite knowing I was good at them and I enjoyed them. Towards the late
November, I had a 3-week stretch when I slept no more than 2 hours a night and
felt like a zombie. That’s when I knew it was serious.
</p>
<p>
In December
<a href="https://hamatti.org/posts/devrelcon-prague-2022-recap/" class="notion-text-href">I traveled to Prague for DevRelCon</a>
and the night before the conference, I had my first proper sleep in 3 weeks in
the hotel. I got a bit of inspiration from the conference and decided to
direct that towards the creation of
<a href="https://www.syntaxerror.tech/" class="notion-text-href">Syntax Error</a>.
</p>
<p>On Christmas Eve lunch with my family, I told them I was coming home.</p>
<h2 class="notion-heading_2 notion-color-default">Recovery has been rough</h2>
<p>
Once again the start was quite good. I moved back home, cleared pretty much
everything from my table and had a full month to take easy before the work
started.
</p>
<p>
During the spring, I was so incredibly tired. I could sleep 14 hours a
night/day (and most days, did). I was so tired I would go straight to bed from
work and sleep until the next morning. If I sat down to watch a movie or
listen to a conference talk like I did in Future Frontend in June, I found
myself falling asleep in 5 minutes no matter how hard I tried to stay awake.
</p>
<p>
At one point, I had horrible back pain and couldn’t sleep well for 2 weeks
despite being super tired.
</p>
<p>I was honestly so scared. I kept wondering if I could ever recover.</p>
<p>
My weight wasn’t dropping and my stamina wasn’t increasing despite walking way
more and eating healthier. That led to me walking less and eating worse
because everything felt so pointless.
</p>
<p>
I knew that there’s no such thing as long-term “sleep debt” that you could
accrue and then pay off but I kept hoping and hoping that it would be it and
one day I’d have paid off the debt of the past 10 years.
</p>
<p>
I’m an average developer at best and I’ve never been good at dealing with
being average at anything. I either do things I’m good at or I don’t do them
at all. So I’ve had a lot of anguish to go through coming into terms with
that. Luckily my teammates have been super supportive and given me all the
space and time I’ve needed. I know you read this so thanks ❤️.
</p>
<h2 class="notion-heading_2 notion-color-default">
Light at the end of the tunnel?
</h2>
<p>
Around last week of July, first week of April I started to notice that my
tiredness was stepping aside. There started to be days when after walking away
from office after a day’s work I wasn’t exhausted and tired and I had energy
to do stuff. The Wednesdays that I took off to take care of myself and focus
on other things than work were no longer spent 20 hours in bed but I had
energy to work on my hobby projects and I started coming up with ideas again
and that reinvigorated me.
</p>
<p>
I’ve started to pick up stuff that I enjoy doing. I started a new mentorship
the other week, I did some startup coaching in my alumni startup accelerator
and in September I’m traveling to PyCon CZ and in October to DjangoDay
Copenhagen to do what I love.
</p>
<p>
Last Thursday, I sat on a bus stop just as the sun set. It was a beautiful,
calm evening with no clouds on the sky, no wind and it was quiet. And I felt
that life was good again.
</p>
<p>
As I started to gain energy, I’ve been looking back at the past 10 years
through photos and blog posts and stories and discussions with friends and
I’ve realized how magical those moments with my communities have been. I’ve
often been too busy to notice it during the moments but looking back, there’s
been so many amazing people, communities, moments and memories there.
</p>
<p>
And all that nostalgic journeying has given me a lot of self-confidence as
I’ve noticed similar patterns and success stories in all the communities I’ve
had the pleasure to build and lead.
</p>
<h2 class="notion-heading_2 notion-color-default">Just in the nick of time?</h2>
<p>
Given how rough and difficult the recovery has been, I keep thinking how close
did it get. If it took 6 months just to stop being ultimately tired 24/7, I
can’t imagine there was too much leeway. I know there’s still a long way ahead
of me to get back to 100%. And I hope when I do, there’s still some career
opportunities waiting for me in the community space.
</p>
<p>
Even though I know it was the right and the only move to leave Mozilla, I
still keep thinking back to how I blew up the best opportunity I had to do my
dream work. I guess that’s part of the process I need to go through until I’m
back on my feet. To accept that sometimes I can’t do it all and I have my
limits, no matter how much I try to lie myself.
</p>
Building Dark Mode for hamatti.org
2023-08-24T00:00:00Z
https://hamatti.org/posts/building-dark-mode-for-hamatti-org/
<p>
I’m one of the people who’s answer to “light or dark mode” is 95% of the time:
“whatever is the default”.
</p>
<p>
This is especially true in web where I often struggle with enjoying dark mode
as most images, photos and in-site graphics feel like they are out of place
with their bright backgrounds. That, alongside the fact that my old website
codebase just made it very hard to even attempt, has kept this site on the
light mode for everyone.
</p>
<p>
<b class="notion-text-bold">Until now!</b>
</p>
<p>
Starting today, <a href="https://hamatti.org/" class="notion-text-href">hamatti.org</a> can be
browsed with dark theme.
</p>
<h2 class="notion-heading_2 notion-color-default">Let there be light</h2>
<p>
By default, the website is on light mode and looks like this at the time of
writing
</p>
<img src="https://hamatti.org/assets/img/posts/building-dark-mode-for-hamatti-org/1.png" alt="Screenshot of hamatti.org main page on light mode " />
<h2 class="notion-heading_2 notion-color-default">
Hello darkness my old friend
</h2>
<p>
And when dark mode is requested, the accent color goes pink, text off-white
and background dark
</p>
<img src="https://hamatti.org/assets/img/posts/building-dark-mode-for-hamatti-org/2.png" alt="Screenshot of hamatti.org main page on dark mode " />
<p>
A really difficult decision was to choose the new accent color since that
dark-ish wine red has become a really big favorite of mine but I’ve always
loved pink and I think it looks pretty cool.
</p>
<h2 class="notion-heading_2 notion-color-default">A look under the hood</h2>
<p>
To start working on the dark mode, I added a
<code class="notion-text-code">data-theme</code> attribute to my
<code class="notion-text-code">body</code> element. That will be used as the
CSS target for different coloring.
</p>
<p>
After that, I added a button at the end of my navigation to allow the user to
manually switch between dark and light modes:
<code class="notion-text-code"><li><button
id="toggleTheme">(icon)</button></li></code>
</p>
<p>The changes are being controlled by a couple of Javascript snippets:</p>
<p>
The first part is two functions that save and load the user preference from
localStorage:
</p>
<pre class="language-javascript"><code class="language-javascript">function saveTheme(theme) {
localStorage.setItem('theme', theme)
}
function loadTheme() {
return localStorage.getItem('theme')
}</code></pre>
<p>Second, a function that handles changing of the theme:</p>
<pre class="language-javascript"><code class="language-javascript">function changeTheme(newTheme) {
document.body.dataset.theme = newTheme
saveTheme(newTheme)
}</code></pre>
<p>
On page load, I check if the user has a preference through localStorage,
browser or operating system settings (using
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme" class="notion-text-href">prefers-color-scheme media query</a>):
</p>
<pre class="language-javascript"><code class="language-javascript">let savedTheme = loadTheme();
if(savedTheme) {
changeTheme(savedTheme)
} else {
let theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark').matches ? 'dark' : 'light'
changeTheme(theme)
}</code></pre>
<p>
Finally, I have event listeners for if the media query changes or user clicks
the button to manually change:
</p>
<pre class="language-javascript"><code class="language-javascript">window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
const newTheme = event.matches ? "dark" : "light";
changeTheme(newTheme)
});
const modeToggle = document.querySelector('button#toggleTheme')
modeToggle.addEventListener('click', ev => {
ev.preventDefault()
changeTheme(document.body.dataset.theme === 'dark' ? 'light' : 'dark')
})</code></pre>
<p>In CSS land, I defined new colors for the dark mode:</p>
<pre class="language-css"><code class="language-css">body[data-theme="dark"] {
--brand: #ffa6b3;
--text: #e6e6e6;
--color-background-primary: #2b2833;
--color-text-primary: white;
--shadow: rgb(228, 220, 220);
}</code></pre>
<p>
It took a lot of tinkering and testing to get the colors right and I’m still
not 100% sure if I’m convinced.
</p>
<p>
One thing that I haven’t done yet but I think I should is to solve the
original issue: I have a lot of in-site graphics that are designed with white
background in mind and pop out in a way that shouts “I don’t belong here” to
my face.
</p>
<p>
Finally, I added a tiny animation to rotate the button 180 degrees while
applying the new theme:
</p>
<pre class="language-css"><code class="language-css">body[data-theme="dark"] button#toggleTheme {
transform: rotate(180deg);
transition: 1s;
}
button#toggleTheme {
transition: 1s;
}</code></pre>
<p>It’s my first ever CSS animation!</p>
<h2 class="notion-heading_2 notion-color-default">Inspiration</h2>
<p>
A big shoutout to a few blog posts that inspired me to finally tackle this
(and for practical tips!)
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://whitep4nth3r.com/blog/best-light-dark-mode-theme-toggle-javascript/" class="notion-text-href">Salma’s The best light/dark mode theme toggle in JavaScript</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://fossheim.io/writing/posts/accessible-theme-picker-html-css-js/" class="notion-text-href">Sarah’s Building an accessible theme picker with HTML, CSS and
JavaScript.</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/" class="notion-text-href">Adhuham’s A Complete Guide to Dark Mode on the Web</a>
</li>
<li class="notion-bulleted_list_item notion-color-default">
<a href="https://sia.codes/posts/how-to-build-a-website/" class="notion-text-href">Sia’s How to build a website in 2021</a>
</li>
</ul>
Let’s talk about Steam Deck
2023-08-23T00:00:00Z
https://hamatti.org/posts/lets-talk-about-steam-deck/
<p>
My first impression of
<a href="https://store.steampowered.com/steamdeck/" class="notion-text-href">Steam Deck</a>
after 10 months or so is:
<b class="notion-text-bold">it’s what I wished Nintendo Switch would have been.</b>
I’ve been a very happy camper with Switch but there were a couple of
annoyances and Valve just swooped in and fixed all of them, adding more
goodness on top and is at this point by far my favourite gaming machine and
more.
</p>
<p>
I could talk for hours of the reasons why I like this device and what makes it
probably the best computer/gaming console ever made. Instead of hours, I’ve
put together some of my favourite features into this handy blog post.
</p>
<h2 class="notion-heading_2 notion-color-default">The Basics</h2>
<p>
I got the <b class="notion-text-bold">512GB NVMe SSD </b>(with exclusive
virtual keyboard theme; don’t ask, I have no clue what it is) model which is
the flagship model. I figured, if I’m buying a system like this, I’ll just get
the one with best specs. With modern gaming, 64GB and 256GB just sounded like
a challenge that would come up way too fast. It is possible to upgrade the SSD
to a larger one but I didn’t want to do that tinkering right now so I was
happy to put in the money and have it delivered with the biggest drive to get
me started.
</p>
<p>
Looking at my storage situation at the time of writing, I have 27.5GB free (I
have an SD card but so far, I’ve not formatted it to be part of the Steam
system, more of it later). I have 59 games/apps installed.
</p>
<p>
I do primarily play indie games which explains the small storage need.
However, I’m now going to need to get a 1TB or 512GB SD card since I’m
starting to run out of space. For a home system, that wouldn’t be a huge issue
as I could just download and remove games as I wish. Given that Steam Deck is
on the go often and especially on longer travels with very limited Internet
(5G roaming or hotel wifi, yikes), I want it to be able to hold a variety of
games and apps inside it for different needs.
</p>
<h2 class="notion-heading_2 notion-color-default">Different modes of gaming</h2>
<p>
One of the aspects of Steam Deck I always bring up in discussions is how I
enjoy its multiple modes of gaming that fit different type of games.
</p>
<p>
Since the device is a self-contained console (meaning it has screen and
controller built-in), it’s a fantastic handheld gaming device. Handhelds are
great for having big adventures on a small screen. Whether you like to play
games sitting on the couch while your partner or roommate is watching TV or
playing for a bit before going to sleep in bed, Steam Deck is a lovely device
to do that.
</p>
<p>
Thanks to its sleep mode that allows you to put the device to sleep in the
middle of the game and get back instantly – no waiting for the device to boot,
Steam to start, game to open and save to load – it’s so convenient to play
even during short time slots: a break at work, waiting for a bus or waiting
for an appointment. I’ve found myself play more than ever because of this
feature.
</p>
<p>
When you’re at home and want to play on the big screen, its standard USB-C
connection enables the use of docks and dongles to connect to HDMI into a TV
or projector and with any bluetooth controller, you can take on big adventures
on big screens. At this point, it’s just like your big home consoles.
</p>
<p>
And finally, you can bring it to your desk, connect to a keyboard, mouse and
display and play all the PC favourites that don’t translate well to
controllers. Strategy games like Age of Empires are right at home with Steam
Deck because it allows you to connect any bluetooth or wired accessories.
</p>
<h2 class="notion-heading_2 notion-color-default">Interconnectivity</h2>
<p>
Almost every bit of this blog post could be a celebration of Valve allowing
Steam Deck to run any software and connect with other devices with standard
bluetooth and usb connections. It’s such a breathe of fresh air in the world
where everyone’s building walled gardens and proprietary connections. (Looking
at you Nintendo, grr)
</p>
<p>
In addition to the vast Steam library, you can install
<a href="https://heroicgameslauncher.com/" class="notion-text-href">Heroic Games Launcher</a>
from the desktop software manager, install your Epic Games and GOG games and
play them through the Steam interface like they are native games to the
system.
</p>
<p>
For PlayStation owners, there’s
<a href="https://sr.ht/~thestr4ng3r/chiaki/" class="notion-text-href">chiaki</a>
that enables playing PlayStation games through PS Remote directly on the deck.
So if you live with other people and the TV is reserved, you can still
continue your PlayStation adventures from Steam Deck.
</p>
<p>
And with Valve’s own
<a href="https://store.steampowered.com/app/353380/Steam_Link/" class="notion-text-href">Steam Link</a>, you can play games on your other systems with Steam Deck and vice versa.
</p>
<h2 class="notion-heading_2 notion-color-default">
Sharing and syncing files across systems
</h2>
<p>
I often find a need to move files between my laptop and Steam Deck and so far,
I’ve gotten into the habit of using these four:
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
<b class="notion-text-bold">microSD card </b>that I can use to move large
files when I don’t have access to my Steam Deck. I have one SD card
dedicated for media content that I take with me when I travel so I have
documentaries and other films to watch.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<b class="notion-text-bold"><a href="https://syncthing.net/" class="notion-text-href">Syncthing</a></b><b class="notion-text-bold"> </b>is a tool that allows you to set up
folders on all your computers and the Steam Deck and when all of them are
online, it syncs the content across the devices. It’s handy for sharing
smaller files, especially when gathering them during the day on the laptop
and then syncing to Steam Deck. It can also be used as a custom cloud save
thing with games that don’t support Steam Cloud Save or with games on the
emulator, enabling you to seamlessly continue your adventures on different
devices.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<b class="notion-text-bold"><a href="https://localsend.org/#/" class="notion-text-href">LocalSend</a></b><b class="notion-text-bold"> </b>is a peer-to-peer local network file share
tool that works like Apple’s AirDrop but better and across all your
regardless of their operating system. I use this only occasionally with
Steam Deck though because it requires to be open, on the desktop mode and
manually accepting the files so it’s less handy.
</li>
<li class="notion-bulleted_list_item notion-color-default">
<b class="notion-text-bold"><a href="https://linux.die.net/man/1/scp" class="notion-text-href">scp</a></b>
is a Linux utility that allows remote file copying across devices. Since
Steam Deck runs Linux and that Linux is up and running even when I play
games, I often use it to move large files from my laptop as it can be done
as long as there’s a SSH server enabled in Steam Deck.
</li>
</ul>
<p>
Since Steam allows addition of any non-Steam application into it as if it was
a native Steam app, you can use Steam Deck as a great media device.
</p>
<p>
I have <a href="https://kodi.tv/" class="notion-text-href">Kodi</a> installed
so I can watch local videos from my micro SD card through a nice and
controller-friendly UI.
</p>
<p>
I’ve also set up browsers in kiosk mode to open in Steam so I have added
Netflix and Youtube as faux “apps” and with the customisable controls, I’ve
made them as nicely usable as possible on the device so I can stream videos
when I have access to Internet.
</p>
<p>
This is also why I keep one micro SD card as a “regular” card, not formatted
to be part of Steam Deck so games won’t install there and I can use it across
all my systems.
</p>
<h2 class="notion-heading_2 notion-color-default">
It’s Linux with a desktop mode!
</h2>
<p>
As a software developer and general geek, I love that I’m able to boot into a
Linux desktop and do whatever I need with computers. I have a full software
development setup so I can write my blog posts, update my website, maintain
and build my hobby projects and beyond.
</p>
<p>
It’s a bit protected system in the beginning to avoid accidental things but it
can be unlocked with full admin access. I’ve
<a href="https://shendrick.net/Gaming/2022/05/30/sshonsteamdeck.html" class="notion-text-href">enabled SSH with key-based login</a>
so I can connect to the system with SSH and scp from my laptop, even when I’m
gaming.
</p>
<p>
One great use case for this has been using it to copy Stardew Valley save
games to my laptop so I can use
<a href="https://mouseypounds.github.io/stardew-checkup/" class="notion-text-href">Stardew Checkup</a>
and keep track of my progress without exiting the game.
</p>
<h2 class="notion-heading_2 notion-color-default">Emulation beast</h2>
<p>
Steam Deck is a wonderful device to emulate old consoles and give your old
game collection a new life – on the go. There are so many great NES, SNES,
GameBoy, GBA, (3)DS, GameCube and other retro console games that are nice on
Steam Deck screen.
</p>
<p>
<a href="https://www.emudeck.com/" class="notion-text-href">EmuDeck</a> makes
installing the system and managing your games a lovely experience and it
allows you to bring in your own artwork so the games look fantastic on the
main Steam gaming mode UI – just like all the other games on your system.
</p>
<p>
Battery life is something you always need to keep in mind with handheld
devices, these old games are not very power hungry so you can explore Hyrule
or Mushroom Kingdom for hours and hours. And the games don’t take almost any
space on the disk.
</p>
<h2 class="notion-heading_2 notion-color-default">Accessories</h2>
<p>
I haven’t gotten too many accessories to Steam Deck, at least not yet. I did
end up buying JSAUX’s dock so I have something on my desk for it to sit on as
the one downside of Steam Deck is that it doesn’t have any sort of kickstand
built in. My dock is connected to my display at desk so I can quickly jump on
a game during a break at work or while sitting on the desk in the evenings.
</p>
<p>
I also got
<a href="https://jsaux.com/products/jsaux-anti-slip-holder-for-steam-deck" class="notion-text-href">their foldable kickstand</a>
that I keep in my backpack so I have a stable and sturdy stand for the device
on the road.
</p>
<p>
Other than those, I just have the case that came with the device. I have a
messenger bag that can fit Steam Deck, my keyboard and mouse and a charger so
I can stay fully prepared for anything on the road.
</p>
<h2 class="notion-heading_2 notion-color-default">What am I playing?</h2>
<p>
So many goodies! I mentioned
<a href="https://hamatti.org/posts/a-bunch-more-of-small-game-reviews-dredge-dishonored-farm-keeper/" class="notion-text-href">earlier this month I’ve been playing DREDGE, Farm Keeper and Dishonored 1 &
2</a>
and just finished Dishonored 1 and I’m about mid-way in the second one. I’ve
also been enjoying a lot of Tony Hawk’s Pro Skater games and I’m considering
purchasing
<a href="https://store.steampowered.com/app/1244090/Sea_of_Stars/" class="notion-text-href">Sea of Stars</a>
as it launches on the 29th.
</p>
<p>
I moved my
<a href="https://store.steampowered.com/app/413150/Stardew_Valley/" class="notion-text-href">Stardew Valley</a>
save and mods from Macbook to Steam Deck as my laptop started running out of
power to keep going with all my mods.
<a href="https://store.steampowered.com/app/11020/TrackMania_Nations_Forever/" class="notion-text-href">TrackMania Nations Forever</a>
is a perfect short session game as each run only lasts a few minutes at tops.
I’m also revisiting early Splinter Cell games.
</p>
<p>
A few games that are fantastic on Steam Deck are
<a href="https://store.steampowered.com/app/1637320/Dome_Keeper/" class="notion-text-href">Dome Keeper</a>,
<a href="https://store.steampowered.com/app/646570/Slay_the_Spire/" class="notion-text-href">Slay the Spire</a>
and
<a href="https://store.steampowered.com/app/460950/Katana_ZERO/" class="notion-text-href">Katana ZERO</a>. For the longest time, I didn’t like roguelike games but on Steam Deck, they
are so good as each run is unique and limited at length so I don’t need to try
to remember what I was in the middle of in the game when I return to it.
</p>
<p></p>
Website rewrite and switching to Notion as CMS
2023-08-22T00:00:00Z
https://hamatti.org/posts/website-rewrite-and-switching-to-notion-as-cms/
<p>
I built this current website with Eleventy 0.8 in early 2019. I would write my
blog posts with Markdown inside VS Code, build with Eleventy and deploy to
Netlify. And for a while, it was good. In early 2020, I made
<a href="https://hamatti.org/posts/how-my-site-is-built-with-eleventy-ghost/" class="notion-text-href">the first bigger upgrade as I started using Ghost as a headless CMS</a>. The main reason for that was to get a better writing and editing experience
for writing blog posts.
</p>
<p>
And it worked wonders for a long time. Somewhere around 2021-2022 I started to
feel the desire to get a bit more customizability out of my CMS but since I
was having a good time writing weekly blog posts, I did most of those by hand
in the downloaded files.
</p>
<p>
Another problem was that I had originally taken the layout from a website that
offered free layouts but the HTML and CSS wasn’t that great, especially given
the lack of proper HTML landmark use. Some of my own additions weren’t very
good for responsive use either so there were a lot of smaller issues like
those in my codebase.
</p>
<p>
In August 2023, I did a complete rewrite of HTML, CSS and Javascript, upgraded
my stack (hello Eleventy 2.0!) and as the final major step, switched from
Ghost to Notion as my headless CMS. The site looks
<i class="notion-text-italic">mostly</i> the same but there are a few visible
changes as well.
</p>
<h2 class="notion-heading_2 notion-color-default">Rewrite of HTML & CSS</h2>
<p>
The first, roughly 50 hours, I spent completely rewriting the website and
bringing in all the content from the old repository. I started with a
completely blank repository and added files one by one to achieve the result I
wanted. During this, I learned I have a
<b class="notion-text-bold">ton of content</b> here.
</p>
<p>
I managed to make the website way better for mobile use and more prepared for
making new changes and updates.
</p>
<img src="https://hamatti.org/assets/img/posts/website-rewrite-and-switching-to-notion-as-cms/1.png" alt="Screenshot of the navigation of this website with Blog link highlighted as current page " />
<p>
The main navigation on the top was improved a lot for mobile, I added a
sidebar to my blog for the first time (only on wider displays) which also
inspired me to properly start using categories/tags – I had added them in my
blog posts but they weren’t visible on the old site. Now you can find more
blog posts of the same category through the sidebar or individual blog posts.
</p>
<p>
As I was working with categories, I read
<a href="https://lea.verou.me/blog/2023/rethinking-categorization/" class="notion-text-href">Lea Verou’s recent blog post from her own website renewal project</a>. I’ve never had made a distinction between categories and tags and mine are
very flat but her thinking especially around the hierarchy of tags (for
example, <code class="notion-text-code">css-backgrounds</code> tag should
always also be tagged <code class="notion-text-code">css</code> but not the
other way around). For me, it’s too early to think about that but as my tag
usage probably improves now, I might consider something along those lines.
</p>
<p>
The sidebar also shows a few of the most recent blog posts (personally I find
it so helpful as I’m always linking recent posts everywhere) and I’m working
on adding
<a href="https://www.thoughtco.com/what-is-blogroll-3476580" class="notion-text-href">a blog roll</a>
to promote blogs from people who write about similar topics and which I enjoy
reading.
</p>
<p>
Another improvement I made is for the navigation and that’s starting to use
<code class="notion-text-code">aria-current="page"</code> with background
highlight to indicate which section the reader is on my site.
</p>
<p>
I also made a lot of different pages and typography and such much more unified
in style as my old approach was all over the place.
</p>
<h2 class="notion-heading_2 notion-color-default">Backend updates</h2>
<p>
Updating to Eleventy 2.0 didn’t bring much visible changes yet to my flow but
one big change I made was to integrate the deployment through GitHub.
</p>
<p>
My old flow was to download blog posts via Ghost API or make changes to static
pages, then manually build with
<code class="notion-text-code">eleventy build</code> (I was still on version
<code class="notion-text-code">0.9</code>...) and deploy to Netlify with
Netlify CLI’s <code class="notion-text-code">netlify deploy</code> (for
preview) and <code class="notion-text-code">netlify deploy --prod</code> (for
production).
</p>
<p>
Now with the new flow, I push changes to GitHub. If there’s a new change in
<code class="notion-text-code">main</code> branch, Netlify will deploy that to
production and update my site. If I make a pull request, Netlify will create a
draft deployment and comments a link (and a QR code for mobile) to my pull
request. I can then take a look, make adjustments and when I merge it in, it
will automatically trigger a production build.
</p>
<p>
A big improvement this gives me is that I can make small fixes like fixing
typos or links or adding missing attributes to posts without needing to have
full environment to make website builds. This was somewhat of an issue with
the old setup because it had not been maintained well so it was hard for me to
make changes to the website with anything but my main laptop.
</p>
<p>
Now I can even make the changed directly through GitHub’s UI and get it
deployed. Or clone the repository on any computer, make a fix and deploy that.
</p>
<p>
Another major improvement this new flow made possible for me was to prepare
blog post releases in a git branch, then make a pull requests and all I need
now to publish my post is to merge the PR from GitHub’s UI and I can do that
on mobile which opens up a lot of new opportunities for me. During this month,
at times I’ve had 3 PRs ready for publishing as I’ve written blog posts ready
to wait for daily publishing.
</p>
<h2 class="notion-heading_2 notion-color-default">
Using global data files with Eleventy
</h2>
<p>
My original build didn’t do much with
<a href="https://www.11ty.dev/docs/data-global/" class="notion-text-href">global data files</a>. However, when building a new site for one of my communities, I used them
extensively since I wanted a more data-driven build for that site. Then I
built a small example/starter kit for a friend with them:
<a href="https://github.com/Hamatti/minimal-11ty-json-example" class="notion-text-href">https://github.com/Hamatti/minimal-11ty-json-example</a>. And when I started doing this rewrite, I decided to use them way more.
</p>
<p>
A big part of that is that they enable me to create a system that can then
later be replaced with a CMS integration if I so desire.
</p>
<h3 class="notion-heading_3 notion-color-default">/uses page example</h3>
<p>
One example of these is my
<a href="https://hamatti.org/uses" class="notion-text-href">/uses page</a>. Instead of writing
all that in Markdown or HTML, I created a JSON file with a structure that
looks like this:
</p>
<pre class="language-json"><code class="language-json">[{
"categoryName": "Software & Services",
"items": [
{
"name": "Mozilla Firefox",
"description": "My main browser choice is Firefox. I want to avoid Google's products as much as I can and Safari never hit off. As an Mozilla alumnus I also have an emotional connection with the Firefox community.",
"url": "https://www.mozilla.org/en-US/firefox/new/"
},
{
"name": "VS Code",
"description": "I use VS Code as one of my code editors.",
"icon": "editor",
"url": "https://code.visualstudio.com/"
},
{
"name": "vim",
"description": "I use vim as another of my code editors.",
"icon": "editor",
"url": "https://www.vim.org/"
},
{
"name": "Notion",
"description": "My main (and one of many) note taking app/service. I keep mostly long-term planning stuff there and do most of my daily note-taking with either a physical notebooks or iPad.",
"url": "https://notion.so"
}
}]</code></pre>
<p>
It’s an array of categories and each category has a
<code class="notion-text-code">categoryName</code> and
<code class="notion-text-code">items</code> and each item contains stuff, of
which most is optional. I then populate my page from these with a Nunjucks
file:
</p>
<pre class="language-html"><code class="language-html">---
layout: layouts/base.njk
title: /uses page
---
<section id="uses">
<h1>/uses page</h1>
<p>Inspired by many other developer's <a href="https://uses.tech/" target=_blank>/uses pages</a>, here's some of my daily tools.</p>
<div>
{% for category in uses %}
<h3>{{category.categoryName}}</h3>
<ul>
{% for item in category.items %}
<li>
<p>
{% if item.url %}
<strong>
<a class="external-link" href="{{item.url}}" target=_blank>{{ item.name }}</a>
</strong>
{% else %}
<strong>{{ item.name }}</strong>
{% endif %}
</p>
<p>{{item.description | safe}}</p>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</section></code></pre>
<p>
Now, if I ever feel the need to manage this data in other means than manually
writing a JSON file, I can manage it with some kind of JSON editor client or
replace it with an API connection, as long as the output JSON matches the
format.
</p>
<p>
And to make that JSON creation faster inside VS Code, I created a custom
snippet for the project:
</p>
<pre class="language-json"><code class="language-json">{
"/uses new entry": {
"scope": "json",
"prefix": "uses",
"body": [
"{\"name\": \"$1\", \"description\": \"$2\", \"url\": \"$3\"}"
],
"description": "Add new entry to /uses JSON"
}
}</code></pre>
<p>
Saved in my <code class="notion-text-code">.vscode/</code> folder as
<code class="notion-text-code">uses.code-snippets</code>, I can invoke it by
typing <code class="notion-text-code">uses</code> + Tab and then fill in the
content.
</p>
<p>
I have other global data files for other similar use cases and at least so far
it feels really good, way better than writing these directly in Markdown or
HTML.
</p>
<p>
They are not a perfect fit for everything though, only for things that are
very structural. I tried same approach when experiment with a /now page but it
didn’t feel good. So /now page will need to wait for another inspiration.
</p>
<h2 class="notion-heading_2 notion-color-default">
A 404 page with featured content
</h2>
<p>
I decided to get the best out of the 404 page by showcasing some of my best
work from my blog. I always felt a “nothing here” page was a missed
opportunity.
</p>
<p>
I added a <code class="notion-text-code">featured: true</code> to a few of my
older blog posts, created a custom collection:
</p>
<pre class="language-javascript"><code class="language-javascript">eleventyConfig.addCollection('featured', (collection) => {
const allPosts = [
...collection.getFilteredByGlob("posts/*.md"),
...collection.getFilteredByGlob("posts/*.njk"),
];
const posts = allPosts.filter(
(post) => post.data.featured && !post.data.draft && !post.data.external_url
);
posts.sort((a, b) => moment(b.data.date) - moment(a.data.date));
return posts
})</code></pre>
<p>and looped over them in my 404 template:</p>
<pre class="language-html"><code class="language-html"><div class="featured-404">
{% for post in collections.featured %}
<div class="featured-post">
<img src="{{ post.data.image }}"/>
<h2>{{ post.data.title }}</h2>
<p>{{post.data.description}}</p>
<div class="featured-cta">
<a href="{{post.url}}">Read the post</a>
</div>
</div>
{% endfor %}
</div></code></pre>
<p>
You can see the result if you visit a page that doesn’t exist, for example
<a href="https://hamatti.org/404" class="notion-text-href">/404</a>.
</p>
<p>
As a side note, I really enjoy how it easy it is to create collections in
Eleventy.
</p>
<h2 class="notion-heading_2 notion-color-default">
The Big Question: Switching my headless CMS
</h2>
<p>
A main reason why it took me so long to get started with the rewrite was that
I felt like getting started with a new CMS and migrating to that would be such
an insurmountable project. I had tried setting up
<a href="https://www.contentful.com/" class="notion-text-href">Contentful</a>
a few times as I had used it at work previously and knew I could do any
content models I wanted. Then I had a discussion with a friend who mentioned
Strapi so I installed that and tried it but setting up those models felt very
time consuming and what was the worst, neither of those had a fantastic editor
experience.
</p>
<p>
I had originally moved to Ghost because it had a very similar experience to
Medium where I disliked almost everything else but loved the editor. Similar
reason why I had used Notion for my note taking and planning for years.
</p>
<p>
And then my friend said this, as a mostly side mention in a more detailed
discussion of other things:
</p>
<blockquote class="notion-quote notion-color-default">
Another unexplored possibility is using Notion API
</blockquote>
<p>
I didn’t make much of it and didn’t comment on it. Until then one night after
I had deployed the other part of the website renewal without too many issues.
I started wondering. “How hard could it be to set up Notion as my headless
CMS?”
</p>
<p>
I used Notion all the time, on all my devices and I was very comfortable with
it. I loved the block-style writing experience, the slick UI and I knew I’d
enjoy writing on it because that was what I had been doing for so long.
</p>
<p>
So I googled: “Using Notion API as headless CMS”. Without the proper
capitalization, of course. I found a few blog posts that focused on the
website side of things (somehow, most of them were Next.js projects, just an
interesting side bit).
</p>
<p>
And I struggled to set things up in Notion. Probably a third of my time was
spent on figuring out how to set it up on Notion and then coding the API
integration part for my blog was rather straight-forward for me, mostly took
time because there was a lot of code to write and testing to do.
</p>
<h3 class="notion-heading_3 notion-color-default">
Setting up headless CMS in Notion
</h3>
<p>
At the time of writing, Notion is on version
<code class="notion-text-code">2.1.15</code> so if you’re reading this way in
the future, some things may have changed. I’ll make a note here if I make any
updates to this due to Notion’s changes.
</p>
<p>
First, create a new page in your workspace and create it as a
<b class="notion-text-bold">Table</b>
</p>
<img src="https://hamatti.org/assets/img/posts/website-rewrite-and-switching-to-notion-as-cms/2.png" alt="New empty page in Notion with Table template highlighted " />
<p>
In Notion, tables and databases are the same and to my understanding is used
indistinguishably from each other in their documentation and guides. After
creating a table, <b class="notion-text-bold">Select a data source </b>and
select <b class="notion-text-bold">+ New database on the right.</b>
</p>
<img src="https://hamatti.org/assets/img/posts/website-rewrite-and-switching-to-notion-as-cms/3.png" alt="Notion new Table page with no data source set " />
<p>
We want to set up a table where each blog post is a row, each column is a
<b class="notion-text-bold">property </b>and by opening the page, we get
access to the page where we can write the blog post itself.
</p>
<img src="https://hamatti.org/assets/img/posts/website-rewrite-and-switching-to-notion-as-cms/4.png" alt=" " />
<p>
What I like about this is that it’s very flexible to add new properties and
edit those properties. Compared to Contentful or Strapi, I don’t need to go to
a different page to set up the content model and then come back and edit all
the blog posts individually.
</p>
<p>
This flexibility comes with a cost. It works for me because I’m playing all
the roles: I maintain the CMS structure, I write the blog posts and I write
the code that integrates it to the website. If any of these three are split
between different people, then I recommend being more careful.
</p>
<h3 class="notion-heading_3 notion-color-default">API integration in Notion</h3>
<p>
Next, you need to add an integration. I found this part very well
<a href="https://developers.notion.com/docs/create-a-notion-integration" class="notion-text-href">documented in Notion’s side</a>
and it’s likely the part in this post that is most likely to change a bit here
and there so I won’t go into step-by-step instructions for that here.
</p>
<p>In nutshell, you need to:</p>
<ol class="notion-numbered_list">
<li class="notion-numbered_list_item notion-color-default">
Create a new Integration
</li>
<li class="notion-numbered_list_item notion-color-default">
Give it Capabilities (I only allowed it to read content as I’m using this
one-way only) (in other terms these define what permissions this integration
has to your Notion)
</li>
<li class="notion-numbered_list_item notion-color-default">
Take note of the Secret Key provided. Never share this with outsiders but
save it to your blog’s environment values.
<b class="notion-text-bold">Do not store this in version control. </b>Anyone
with access to this secret will be able to read what you have in your
connected Notion database.
</li>
<li class="notion-numbered_list_item notion-color-default">
Connect your blog table created earlier with this new integration.
</li>
<li class="notion-numbered_list_item notion-color-default">
Copy database ID (I wish they made it more visible somewhere, now you need
to copy the link and extract it from there) and store that to your
environment values as well.
</li>
</ol>
<h3 class="notion-heading_3 notion-color-default">
Fetching blog posts in your backend
</h3>
<p>My workflow is as follows:</p>
<ol class="notion-numbered_list">
<li class="notion-numbered_list_item notion-color-default">
I write a blog post in Notion
</li>
<li class="notion-numbered_list_item notion-color-default">
I run a command <code class="notion-text-code">npm run notion</code> in my
terminal which runs a script I’ve saved in
<code class="notion-text-code">_scripts/notion.js</code>. This fetches the
blog post(s) from Notion API, creates
<a href="https://www.11ty.dev/docs/data-frontmatter/" class="notion-text-href">front matter</a>
based on the properties (columns in the table) and renders HTML from the
blog content and combines them into a file that it saves to
<code class="notion-text-code">/posts/</code> from where Eleventy builds
them into my blog and RSS feed.
</li>
</ol>
<pre class="language-javascript"><code class="language-javascript">// Get Notion client for calling the API
const { Client } = require("@notionhq/client");
// Read environment values from .env file
require("dotenv").config();
// Initialize Notion client with the auth secret key
const notion = new Client({
auth: process.env.NOTION_SECRET,
});
// A function to download posts (minimal example)
async function getPosts() {
const myPage = await notion.databases.query({
database_id: process.env.NOTION_BLOG_DB_ID
});
// Only return posts that are not drafts and have been published
return myPage.results.filter(
(post) => !post.properties.draft.checkbox && post.properties.published.checkbox
);
}</code></pre>
<p>
I have built a few extra features for my blog, for example if I pass on a
Notion page ID to my command, it will only fetch that and does it regardless
of its draft or published status. This allows me to fetch posts during writing
and editing process to see how it looks like in site while writing.
</p>
<pre class="language-javascript"><code class="language-javascript">// My main function that is run when `npm run notion` is called
async function run() {
// Get all the posts from function above
const posts = await getBlogPosts();
await Promise.all(
// For each post
posts.map(async (post) => {
let { id, properties, url: notion_url } = post;
// Get all the blocks for this page
let resp = await notion.blocks.children.list({
block_id: id,
});
let { results } = resp;
// Query limits blocks to 100 so let's loop until
// we have all of them
while (resp.has_more) {
resp = await notion.blocks.children.list({
block_id: id,
start_cursor: resp.next_cursor,
});
results = [...results, ...resp.results];
}
let {
title,
description,
published_at,
feature_image,
draft,
slug,
tags,
mastodon_id,
} = properties;
// Notion's data is very detailed and inside objects
// that are not documented so well.
// I console.log'd each one, then experimented to
// to find out how to get the actual content
title = title.title[0].plain_text || "";
description = description.rich_text[0]?.plain_text || "";
published_at = published_at.date.start;
draft = draft.checkbox;
slug = slug.rich_text[0]?.plain_text;
// If I forgot to create a slug, create one
if (!slug) {
slug = slugify(title);
}
currentSlug = slug;
tags = tags.multi_select.map((ms) => ms.name);
mastodon_id = mastodon_id.rich_text[0]?.plain_text || "";
// I create front matter by taking in these properties
// and printing them inside triple-dash to match front
// matter format
const frontmatter = FRONTMATTER_TEMPLATE({
title, description, published_at, feature_image: path.join(slug, imageName), draft,
slug, tags, mastodon_id, notion_url, notion_post_id: id,
});
// to be continued...</code></pre>
<p>
A note about how Notion works. What powers Notion is its Block style editor.
Each paragraph is a paragraph block, each image is an image block and so on.
When you type <code class="notion-text-code">/</code> inside Notion, it will
give you options to choose from all sorts of blocks.
</p>
<p>
To render content from these to HTML takes a bit of work. Fortunately, it’s
been mostly done for us by Kerwan who’s built
<a href="https://github.com/kerwanp/notion-render" class="notion-text-href">notion-render Javascript package</a>.
</p>
<pre class="language-javascript"><code class="language-javascript"> // continues from above
// you can do this on top level of your file, I added it here for context
const { NotionRenderer } = require("@notion-render/client");
const renderer = new NotionRenderer()
let html = await renderer.render(...results);
// I do additional formatting to make it prettier
const prettier = require("prettier");
html = await prettier.format(html, { parser: "html" });
// Combine front matter with rendered HTML
html = `${frontmatter}\n${html}`;
// Write to file
const blogPostFile = path.join("posts", `${slug}.njk`);
fs.writeFileSync(blogPostFile, html);
}
)
}</code></pre>
<p>
Above is a bit simplified example that probably cannot be run via copy-paste
as-is because I cleaned up a lot of my custom things for how I like to format
things and all the conditionals to make sure it reacts well to different
cases. But I hope it gives you and idea of what’s involved.
</p>
<h3 class="notion-heading_3 notion-color-default">How I deal with alt text?</h3>
<p>
There’s one glaring accessibility issue with Notion:
<b class="notion-text-bold">you cannot add </b><b class="notion-text-bold"><a href="https://www.a11y-collective.com/how-to-write-great-alt-text/" class="notion-text-href">alt text</a></b><b class="notion-text-bold"> to images</b>. What I’ve done is to use Notion’s
image caption mechanism and then I’ve written a custom image renderer to
Kerwan’s renderer:
</p>
<pre class="language-javascript"><code class="language-javascript">const { createBlockRenderer } = require("@notion-render/client");
// Create your own renderer
const imageRenderer = createBlockRenderer("image", async (data, renderer) => `<img
src="${imagePath}"
alt="${data.image.caption[0]?.plain_text || ""}"
/>;`
)
// Replace the original NotionRenderer initialization with this
const renderer = new NotionRenderer({
renderers: [imageRenderer],
});</code></pre>
<p>
Now I can write captions in Notion that are then used as alt text when the
HTML is rendered. It’s also kinda nice because I can now immediately see on a
glance which images in my blog posts are still missing their alt text.
</p>
<p>I also do a few other things that I omitted from above:</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">
Inside my <code class="notion-text-code">imageRenderer</code>, I also
download the images from Notion to local folder and then use that image path
in the <code class="notion-text-code"><img></code>
</li>
<li class="notion-bulleted_list_item notion-color-default">
I have a custom <code class="notion-text-code">codeRenderer</code> as well
to make my code blocks more compatible with my Prism.js highlighting
</li>
<li class="notion-bulleted_list_item notion-color-default">
I add the Notion page ID and URL to each blog post’s front matter to make it
easier for me to edit the contents (by clicking the URL) and redownload the
post (with the ID). They are not used anywhere in the built HTML that users
see.
</li>
<li class="notion-bulleted_list_item notion-color-default">
I have a few extra options I can pass to my script to better control what I
want to download from Notion.
</li>
</ul>
<p>
A few weeks ago, I built a
<a href="https://hamatti.org/posts/blog-comments-via-mastodon/" class="notion-text-href">Mastodon comment system</a>
to my blog. The way this works is I first write the blog post and publish it.
No comment section is visible. I then make a Mastodon post about it, copy the
post ID and add it to the properties in Notion table. Finally, I redownload
the post to get the ID to my final version and publish again. Comments are now
available!
</p>
<p>
If I ever want to post a blog post without enabling commenting (or remove
later), I leave the <code class="notion-text-code">mastodon_id</code> column
empty for that blog post. I’ve planned to have an addition that allows me to
moderate individual replies by adding their IDs to a list but I’ll make that
happen
<a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it" class="notion-text-href">once I actually need it for the first time</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">
What happens if Notion goes down?
</h2>
<p>
The way I’ve built my headless CMS integration both with Ghost and now with
Notion is that if something happens to my CMS (servers go down, company goes
bankrupt or something else), the only thing I lose is my drafts that I haven’t
backed up.
</p>
<p>
Since I always copy the content to my local code base and don’t download it at
build time but as a pre-build step, I won’t lose any downloaded blog posts. I
also always have the possibility to write a blog post as a pure Markdown file
– they are treated equally in my build process to the ones downloaded from
Ghost or Notion.
</p>
<h2 class="notion-heading_2 notion-color-default">
First impressions with the new setup
</h2>
<p>
My first impressions are great. The build & deployment workflow feels so much
lighter, Notion feels amazing to write blog posts in and I’m excited for its
customizability. I also built a similar integration for my
<a href="https://hamatti.org/weeklies" class="notion-text-href">Weeklies</a> and it’s a way
simpler one: there’s no main content, everything is stored in table rows as
properties and then rendered into a list on the website.
</p>
<p>
I built
<a href="https://hamatti.org/posts/blog-comments-via-mastodon/" class="notion-text-href">Mastodon comment system</a>
already before the rewrite but it’s been really nice too, I’ve been happy to
see people comment and then have those comments appear on the site. I hope it
will encourage people to comment more: seeing it in blog may encourage someone
to jump to Fediverse to comment or someone seeing it in Mastodon may get
encouraged to read the blog.
</p>
<p>
One thing I like with Notion over Ghost is that I can do partial links. In
Ghost, the hyperlink mechanism had a validator to only accept full links. With
Notion, I can add a link to <code class="notion-text-code">/blog</code> so it
will always refer to the correct environment (dev, preview, production).
</p>
<p>
Thanks to Notion’s structured Block editor, downloading images from blog posts
and storing them in correct place and fixing the source links is a delight
with those custom renderers. I tried doing it with Ghost initially but
couldn’t make it robust so I skipped it. What it means to me is I have to
manually migrate ~500 images in blog posts from Ghost to my local code base.
</p>
<p>
Most importantly with Notion, I can now finally add all the extra properties
to my blog post metadata that I couldn’t with Ghost, making it possible for me
to build new features.
</p>
<p>
One feature I already have built but not super much in use yet is related
posts. For some older blog posts (and during this rewrite, I added bunch
manually) I already have a
<code class="notion-text-code">related_posts</code> field where I can add
other blog posts and they get shown in the sidebar of the blog post! Now with
Notion, I can start doing that inside Notion table and even do it with
<a href="https://www.notion.so/help/relations-and-rollups" class="notion-text-href">dynamic relations</a>, linking to other entries in the table.
</p>
<p>
With the new and fresh code base, I feel more excited than ever to start
working on new features to take it to even next level, while continuing to
have a great writing experience that will help me maintain my regular blogging
schedule.
</p>
<p>
One downside of Notion is that it’s not open source and I can’t self-host it,
meaning I’m at the mercy of the company for the future. But as I noted in the
section above, the worst is I need to write new blog posts in Markdown while
setting up a new headless CMS.
</p>
Stardew Valley Mods I use
2023-08-21T00:00:00Z
https://hamatti.org/posts/stardew-valley-mods-i-use/
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/1.png" alt=" " />
<p>
<a href="https://store.steampowered.com/app/413150/Stardew_Valley/" class="notion-text-href">Stardew Valley</a>
is a lovely farming and adventure game created by ConcernedApe. It originally
came out in February of 2016 and has gotten a few quite big updates ever since
with the most recent, 1.5 update adding a whole new end game story content
that added almost another full game worth of new things to do.
</p>
<p>
However, I have spent almost 700 hours with the game so playing through the
base game doesn’t cut it anymore. So little by little, I’ve started adding
mods and right now I have a really nice playthrough going on with a bunch of
mods. I had to move the save file and mods from my Macbook to Steam Deck
because my Mac just can’t run it anymore with this amount of mods loaded.
</p>
<p>
I recommend playing the base game without mods until you start to grow a bit
bored with the game or annoyed with some repetitive stuff and then look into
mods one by one.
</p>
<h2 class="notion-heading_2 notion-color-default">
Visual or quality of life mods
</h2>
<h3 class="notion-heading_3 notion-color-default">UI Info Suite 2</h3>
<p>
The first mod is a quality of life mod that doesn’t change anything in the
gameplay itself, just makes certain things more conveniently presented in the
UI. Things like daily luck, tomorrow’s weather, current birthdays and which
animals need petting are all presented in view, saving a lot of monotonous
activity that doesn’t really contribute to the game at this stage anymore.
</p>
<p>
<a href="https://github.com/Annosz/UIInfoSuite2" class="notion-text-href">UI Info Suite 2</a><b class="notion-text-bold"> </b>has a nice amount of customizability to make
it fit your needs. For me, the biggest improvement it brings in the early game
is the experience meters for different skills. The first 40-50 days of any
playthrough are crucial with the skills (especially hitting lvl 5 fishing and
lvl 6 farming and gaining a level to avoid exhaust penalties) and with this
mod, I don’t have to guess where I am with my progression.
</p>
<h3 class="notion-heading_3 notion-color-default">Automate</h3>
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/2.png" alt="Annotated screenshot of crab pots connected to a bait machine, chest and trash recycler in Stardew VAlley " />
<p>
One of the thing I grew bored of in the game was having huge amount of
machines like kegs, cheese presses and so on and having to walk past all of
them constantly and picking products and inserting new ones.
</p>
<p>
So I use
<a href="https://www.nexusmods.com/stardewvalley/mods/1063" class="notion-text-href">Automate mod</a>
that automates all of that: you place a chest next to machines and they will
automatically process goods from the chest and put the finished product back
and repeat this as long as there’s goods to use. I like this when mining in
the Mines as I can leave a bunch of furnaces near a chest and dump my ore and
coal to the chest every 5 levels and keep churning good amount of bars.
</p>
<p>
There’s already a few things in the game you can “automate” like sprinklers to
avoid watering and Junimo huts to avoid gathering crops and this mod adds a
whole new level to it, making the game more enjoyable to me at this point.
</p>
<h3 class="notion-heading_3 notion-color-default">Activated Sprinklers</h3>
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/3.png" alt="Screenshot of Stardew Valley with an iridium sprinkler being activated and watering crops. " />
<p>
Watering crops is the #1 thing I’m annoyed by in this game. I basically always
focus on getting to basic and then quality sprinklers as quick as I can to
avoid watering. However, you still need to often water the first day of new
season or new patch of land if there’s untilled soil that existing sprinklers
haven’t watered.
</p>
<p>
With
<a href="https://www.nexusmods.com/stardewvalley/mods/8688" class="notion-text-href">Activated Sprinklers</a>, I can just click on a sprinkler to activate it, saving a ton of time and
boring watering, making growing crops actually quite nice.
</p>
<h3 class="notion-heading_3 notion-color-default">Minecart Patcher</h3>
<p>
In my most recent playthrough, I started using
<a href="https://www.nexusmods.com/stardewvalley/mods/11424" class="notion-text-href">Minecart Patcher</a>
since with all the story mods (see below), the map started to grow quite big
and this game isn’t really built for quick traveling.
</p>
<p>
The regular minecarts take you between bus stop, east edge of the town, mines
and quarry. With Minecart Patcher, I can also travel to a few extra locations
like Secret Woods, Cindersap Forest, Beach, Desert, East Scarp (mod),
Ridgeside Village (mod) and a few other nice locations.
</p>
<h2 class="notion-heading_2 notion-color-default">Story/content mods</h2>
<h3 class="notion-heading_3 notion-color-default">Stardew Valley Expanded</h3>
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/4.png" alt="Stardew Valley Expanded " />
<p>
<a href="https://www.nexusmods.com/stardewvalley/mods/3753" class="notion-text-href">Expanded</a>
basically adds a whole new game to the mix. It’s such a well-made mod that
adds new characters, new locations, new stories and new gameplay mechanics.
Combined with a few new farm maps that provide a much more lively farm, I
would never play again without Expanded mod.
</p>
<p>
Through all of its new content, it breathes a whole new life to the game. You
can add it to an existing save (although you can’t switch the farm to a newer
one as that breaks stuff) or start a new one from scratch.
</p>
<h3 class="notion-heading_3 notion-color-default">Ridgeside Village</h3>
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/5.png" alt="Ridgeside Village " />
<p>
If Expanded feels a bit too much or not enough,
<a href="https://www.nexusmods.com/stardewvalley/mods/7286" class="notion-text-href">Ridgeside Village</a>
is another story addition that adds a whole new village, accessible from the
bus stop, with new characters, stories and adventures. The characters fit the
lore and the world of Stardew so well.
</p>
<p>
It’s compatible with existing saves. It uses a “X days since start or install”
mechanic for story progression so if you add it on your 2nd year for example,
it won’t immediately trigger everything in the story progression but adapts to
the situation so smoothly.
</p>
<p>
It’s also compatible with the Expanded mod and everything works really well.
</p>
<h3 class="notion-heading_3 notion-color-default">East Scarp</h3>
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/6.png" alt="East Scarp " />
<p>
To the east of Stardew Valley, beyond the Museum and the river, there’s
<a href="https://www.nexusmods.com/stardewvalley/mods/5787" class="notion-text-href">East Scarp</a>
with its oceanside town and cave and a whole new adventure. I’m still
exploring East Scarp on my first playthrough so even I don’t yet know all the
things that await me there.
</p>
<h3 class="notion-heading_3 notion-color-default">
Lunna - Astray in Stardew Valley
</h3>
<img src="https://hamatti.org/assets/img/posts/stardew-valley-mods-i-use/7.png" alt="Lunna Astray in Stardew Valley " />
<p>
<a href="https://www.nexusmods.com/stardewvalley/mods/6626" class="notion-text-href">Lunna</a>
is a mod that adds a new storyline focused on a girl with a secret past and
claims to not come originally from our land but instead from a place called
Umuwi.
</p>
<p>
I’ve only recently met Lunna and started to learn her story but I’m so eager
to visit Umuwi and become friends with the new member in the community.
</p>
<p></p>
Youtubers I watch
2023-08-20T00:00:00Z
https://hamatti.org/posts/youtubers-i-watch/
<p>
There are a lot of good channels in Youtube but every single viewer only sees
a small fraction of them because of how the algorithm keeps making the content
filter bubbles more tight. When I read blogs, I mostly read technical content.
When I watch Youtube, I usually avoid developer content as it’s my escape
hatch from the profession.
</p>
<h2 class="notion-heading_2 notion-color-default">Tricky Gym</h2>
<img src="https://hamatti.org/assets/img/posts/youtubers-i-watch/1.png" alt=" " />
<p>
The best Pokemon TCG content in the web is
<a href="https://www.youtube.com/@TrickyGym" class="notion-text-href">Andrew Mahone’s Tricky Gym</a>. Andrew is a accomplished competitive player and a very entertaining content
creator. I really like that despite his channel growing a lot, he hasn’t
sacrified the quality – quite the opposite. His recent increase in quantity
and quality of tabletop games sets him far apart from all the other creators
in the scene.
</p>
<p>
A few other good Pokemon TCG Youtubers I follow are
<a href="https://www.youtube.com/@AzulGG" class="notion-text-href">Azul GG</a>,
<a href="https://www.youtube.com/@ForTheWinTCG" class="notion-text-href">ForTheWinTCG</a>
and
<a href="https://www.youtube.com/@OmniPoke" class="notion-text-href">Omnipoke</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Berm Peak</h2>
<img src="https://hamatti.org/assets/img/posts/youtubers-i-watch/2.png" alt=" " />
<p>
During the pandemic, I discovered Seth Alvo’s mountain biking channel
<a href="https://www.youtube.com/@SethsBikeHacks" class="notion-text-href">Berm Peak</a>
and its extra channel
<a href="https://www.youtube.com/@BermPeakExpress" class="notion-text-href">Berm Peak Express</a>
and even though I don’t mountain bike, I’ve found a lot of enjoyment through
Seth’s videos. His backyard trail building videos, workspace/office garage
build videos and other entertaining content are such a great comfort watching
on a rainy day.
</p>
<p>
Other mountain bike channels I’ve started following since are
<a href="https://www.youtube.com/@PorterMTB" class="notion-text-href">PorterMTB</a>,
<a href="https://www.youtube.com/@The_Sampler" class="notion-text-href">The Singletrack Sampler</a>
and <a href="https://www.youtube.com/@bkxc" class="notion-text-href">BKXC</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Brandon James Greer</h2>
<img src="https://hamatti.org/assets/img/posts/youtubers-i-watch/3.png" alt=" " />
<p>
<a href="https://www.youtube.com/@BJGpixel" class="notion-text-href">Brandon</a>
is the best pixel art Youtuber out there. Every one of his videos tell an
interesting story and show the process of starting with an empty canvas and
ending up with the end result. I’ve tried my hand at pixel art a few times and
Brandon’s channel has been a definitive inspiration in that.
</p>
<p>
I especially like his set pieces with vibrant urban environments and I loved
his
<a href="https://www.youtube.com/watch?v=uFnL5X_GrmE" class="notion-text-href">Princess Mononoke GBA piece</a>. The way he imagines retro pixel art game scenes from existing media like
Star Wars, Ghibli or Blade Runner gives me a smile as I imagine further what
those games could have been.
</p>
<h2 class="notion-heading_2 notion-color-default">Game Maker’s Toolkit</h2>
<img src="https://hamatti.org/assets/img/posts/youtubers-i-watch/4.png" alt=" " />
<p>
<a href="https://www.youtube.com/@GMTK" class="notion-text-href">Mark Brown’s GMTK</a>
is one of the highest quality channels that makes videos about games. GMTK has
such a unique style and approach to games: it’s not about gameplay videos nor
game reviews but rather well written and researched video essays (that are
also just the right length rather than some 4-hour long essays online that can
be rough sometimes to get through).
</p>
<h2 class="notion-heading_2 notion-color-default">No Rolls Barred</h2>
<img src="https://hamatti.org/assets/img/posts/youtubers-i-watch/5.png" alt=" " />
<p>
Board gaming is living its hay day right now and that’s visible in the amount
of great board gaming Youtube channels.
<a href="https://www.youtube.com/@NoRollsBarred" class="notion-text-href">No Rolls Barred</a>
stands apart from the others with well produced videos of a group of friends
playing games and bantering. I’ve learned of so many interesting games through
the channel’s gameplay videos.
</p>
<p>
It fills the void that
<a href="https://www.youtube.com/playlist?list=PL7atuZxmT955LMr27MkQzrV2adQtU3o5U" class="notion-text-href">Geek & Sundry’s TableTop</a>
left on Youtube and the NRB crew has perfectly filled that slot without trying
to be TableTop. Some other board game channels I like are
<a href="https://www.youtube.com/@BeforeYouPlay" class="notion-text-href">Before You Play</a>,
<a href="https://www.youtube.com/@rahdo" class="notion-text-href">Rahdo</a>,
<a href="https://www.youtube.com/@NoPunIncluded" class="notion-text-href">No Pun Included</a>,
<a href="https://www.youtube.com/@shutupandsitdown" class="notion-text-href">Shut Up and Sit Down</a>
and
<a href="https://www.youtube.com/@boardgamegeek" class="notion-text-href">BoardGameGeek</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Laura Kampf</h2>
<img src="https://hamatti.org/assets/img/posts/youtubers-i-watch/6.png" alt=" " />
<p>
My favorite maker channel on Youtube is by far
<a href="https://www.youtube.com/@laurakampf" class="notion-text-href">Laura Kampf</a>. Bit like with mountain biking, I’m not a maker myself and couldn’t hold a
saw the right way in my hand if my life dependent on it but Laura’s videos
feature so well-crafted storytelling that it captivates me without me having
to watch through 30 minutes of cutting planks of wood and then nailing them
together.
</p>
<p>
Some other maker channels I enjoy are
<a href="https://www.youtube.com/@Iliketomakestuff" class="notion-text-href">I Like To Make Stuff</a>,
<a href="https://www.youtube.com/@craftedworkshop" class="notion-text-href">Crafted Workshop</a>,
<a href="https://www.youtube.com/@MariusHornberger" class="notion-text-href">Marius Hornberger</a>,
<a href="https://www.youtube.com/@achappel" class="notion-text-href">Alexandre Chappel</a>
and
<a href="https://www.youtube.com/@StuffMadeHere" class="notion-text-href">Stuff Made Here</a>.
</p>
<h2 class="notion-heading_2 notion-color-default"></h2>
<p></p>
Bloggers I read: Saturday
2023-08-19T00:00:00Z
https://hamatti.org/posts/bloggers-i-read-saturday/
<p>
I follow over a 100 tech blogs in my RSS reader actively and this week I’ve
picked ~25 of my favorites to share with you. The blogs are not in any
particular order neither by day nor within a single day. Today I wrap up with
the last batch of bloggers!
</p>
<h2 class="notion-heading_2 notion-color-default">Zach Leatherman</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-saturday/1.png" alt=" " />
<p>
This entire website and blog would be way less fun to build and maintain if it
wasn’t for
<a href="https://www.zachleat.com/" class="notion-text-href">Zach</a>. He’s
the creator of arguably the best static site generator, Eleventy that I use
for not only this site but pretty much all my projects these days.
</p>
<p>
Zach is great at communicating what’s happening in the Eleventy project and
writes really nice articles about different facets of web development.
</p>
<h2 class="notion-heading_2 notion-color-default">Lars Wirzenius</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-saturday/2.png" alt=" " />
<p>
Lars has been around the block in the development world quite a bit. He writes
really good technical blog posts, these days mostly about Rust related topics.
There’s
<a href="https://blog.liw.fi/posts/2023/clap/" class="notion-text-href">this recent one on building CLI interfaces with clap</a>
but also stories from the industry like
<a href="https://lwn.net/Articles/928581/" class="notion-text-href">this one published in LWN.net about (the very) early days of Linux</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Juho Lehtinen</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-saturday/3.png" alt=" " />
<p>
<a href="https://lehtu.github.io/" class="notion-text-href">Juho’s blog</a> is
by far the newest one on this list as he started this month. He’s been doing a
lot of interesting things and I’ve been trying to encourage him to start a
blog and he finally did! It’s still in its early days but I can’t wait to read
more about
<a href="https://chrome.google.com/webstore/detail/adhd-reader/gcbchiambcokahmamemkoadlanaealmf" class="notion-text-href">Juho’s ADHD Reader Chrome extension</a>
or his other projects.
</p>
<h2 class="notion-heading_2 notion-color-default">Harry Roberts</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-saturday/4.png" alt=" " />
<p>
Harry’s domain,
<a href="http://csswizardry.com/" class="notion-text-href">CSSWizardry.com</a>
does justice to his vast skill set in building performant websites and helping
others do the same. I’ve learned so much from him over the years on CSS and
then more recently on performance.
</p>
<p>
Whenever I’m in need for tips for those issues, I first check Harry’s site and
then search the web only as a secondary option if I can’t find what I need
from the blog.
</p>
<p>
He has the knack for writing concise technical explanations that still cover
all the necessary bits and have good code examples that focus on the thing at
hand.
</p>
Bloggers I read: Friday
2023-08-18T00:00:00Z
https://hamatti.org/posts/bloggers-i-read-friday/
<p>
I follow over a 100 tech blogs in my RSS reader actively and this week I’ve
picked ~25 of my favorites to share with you. The blogs are not in any
particular order neither by day nor within a single day. As we head over to
the weekend, it’s day four of this miniseries.
</p>
<h2 class="notion-heading_2 notion-color-default">
<b class="notion-text-bold">Morten Rand-Hendriksen</b>
</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-friday/1.png" alt=" " />
<p>
I think it was
<a href="https://mor10.com/" class="notion-text-href">Morten</a>’s great blog
post
<a href="https://mor10.com/blogging-is-dead-long-live-ephemerality/" class="notion-text-href">Blogging is dead. Long live ephemerality.</a>
a few years back that caught my attention. It gave me a major push to focus on
sharing my stuff mainly on my website and only additionally on social medias.
Around the same time, his
<a href="https://www.youtube.com/playlist?list=PLSjIkEzXb6SaufYBk03gMr26ASBPHbfuK" class="notion-text-href">Christmas Crimes playlist</a>
became a Christmas favorite of mine. That’s why I’ve been reading Morten’s
writings ever since.
</p>
<h2 class="notion-heading_2 notion-color-default">Rach Smith</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-friday/2.png" alt=" " />
<p>
I really enjoy
<a href="https://rachsmith.com/" class="notion-text-href">Rach</a>’s style of
writing. Her blog is a combination of technical writing and interesting pieces
on her personal experiences of worklife, parenthood, well-being and
productivity. One of her tech posts that I want to highlight is
<a href="https://rachsmith.com/reading-code-is-different-to-writing/" class="notion-text-href">Reading code is a different experience to writing code</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Niko Heikkilä</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-friday/3.png" alt=" " />
<p>
<a href="https://nikoheikkila.fi/" class="notion-text-href">Niko</a> is a
software craftsperson with a capital C. He’s passionate about testing,
pair/mob programming and building great solutions to real life problems. I got
to work with Niko for a good few years and although I didn’t get a chance to
be in a same team and learn from him through that, I was always excited to
read his well argumented discussion points on these topics.
</p>
<h2 class="notion-heading_2 notion-color-default">Lucia Nazzaro</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-friday/4.png" alt=" " />
<p>
<a href="https://www.oh-no.ooo/" class="notion-text-href">Lucia</a> is
wonderful and amazing human being and a great developer and teammate. And
she’s recently started writing these great blog posts on frontend web topics.
These posts, like
<a href="https://www.oh-no.ooo/" class="notion-text-href">Performance optimization with useMemo</a>, are full of well-researched details, pointers and experiences from other
people’s blogs and talks and they are written in such upbeat positive tone
that I can’t help but to truly enjoy every time Lucia’s blog post hits my RSS
feed.
</p>
<h2 class="notion-heading_2 notion-color-default">Josh W. Comeau</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-friday/5.png" alt=" " />
<p>
<a href="https://www.joshwcomeau.com/" class="notion-text-href">Josh’s</a>
guide
<a href="https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/" class="notion-text-href">An Interactive Guide to Flexbox</a>
is one that truly elevated my understanding and practical skills with flexbox
to a whole new level. After reading the guide - which is filled with amazing
custom tech like his interactive examples - I gained a lot of confidence with
flexbox and have been building so many better things on my projects than I
used to.
</p>
<p>
His
<a href="https://css-for-js.dev/" class="notion-text-href">CSS for Javascript Developers</a>
web course has been on my backlog since its launch and I’m waiting for the
right moment and mindset when I can dive deep into its contents.
</p>
Syntax Error #6: Debugging CSS
2023-08-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-6-css/
Syntax Error is a newsletter about debugging for developers, students,
hobbyists, curious and duck fans. In this August issue of Syntax Error we look
at CSS and various ways to debug it. Read full article at
<a href="https://www.syntaxerror.tech/syntax-error-6-debugging-css/">syntaxerror.tech/syntax-error-6-debugging-css/</a>
and either subscribe to the email or RSS feed to catch all of them.
Bloggers I read: Thursday
2023-08-17T00:00:00Z
https://hamatti.org/posts/bloggers-i-read-thursday/
<p>
I follow over a 100 tech blogs in my RSS reader actively and this week I’ve
picked ~25 of my favorites to share with you. The blogs are not in any
particular order neither by day nor within a single day. It’s day 3 and we’re
in the middle group.
</p>
<h2 class="notion-heading_2 notion-color-default">Sia Karamalegos</h2>
<p></p>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-thursday/1.png" alt=" " />
<p>
<a href="https://sia.codes/" class="notion-text-href">Sia</a> is a
co-organizer of the fantastic
<a href="https://eleventymeetup.netlify.app/" class="notion-text-href">Eleventy meetup</a>
and a great blogger in the web dev space. She has amazing articles in her blog
about web performance and if you’re building anything frontend web related,
you should head over there to read them after finishing with this article.
</p>
<p></p>
<h2 class="notion-heading_2 notion-color-default">Jesse Skinner</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-thursday/2.png" alt=" " />
<p>
<a href="https://www.codingwithjesse.com/" class="notion-text-href">Jesse</a>
is a tech blogger and Youtuber who has a great variety of web development
related blog posts and videos. One of my favorites is
<a href="https://www.codingwithjesse.com/blog/debugging-a-slow-web-app/" class="notion-text-href">his in-depth story of debugging a slow web app for a client</a>.
</p>
<p>
He is also the author of
<a href="https://www.joyofsvelte.com/" class="notion-text-href">Joy of Svelte online course</a>
(that I have not bought, so can’t give a review of that) that teaches you a
lot of Svelte from basics to practical production use.
</p>
<h2 class="notion-heading_2 notion-color-default">Hidde de Vries</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-thursday/3.png" alt=" " />
<p>
<a href="https://hidde.blog/" class="notion-text-href">Hidde’s blog</a> is one
of my go-to places for really good accessibility and CSS blog posts. In
addition to his blog, he also actively shares great learnings and bits of
information
<a href="https://front-end.social/@hdv" class="notion-text-href">in Mastodon</a>.
</p>
<p>
One thing that truly sets his blog posts apart from the huge mass of content
online is the depth and quality of each one of them. He’s very detailed with
the technical details while writing in a way that flows nicely and is easy and
enjoyable to read.
</p>
<h2 class="notion-heading_2 notion-color-default">Floor Drees</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-thursday/4.png" alt=" " />
<p>
<a href="https://floord.github.io/" class="notion-text-href">Floor</a> is
awesome. Do I need to say more? Maybe I’ll say a bit more. She’s involved in a
lot of great developer communities in Netherlands organizing events like
Devopsdays Amsterdam and Devopsdays Eindhoven and seems to be on every great
tech podcast and publication I follow. I’m also very happy to know Floor
outside just the Internet as well and every time I meet her, I get super
inspired by all things dev community.
</p>
<p></p>
<h2 class="notion-heading_2 notion-color-default">Eeva-Jonna Panula</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-thursday/5.png" alt=" " />
<p>
Continuing on the accessibility topic,
<a href="https://eevis.codes/" class="notion-text-href">Eevis</a> writes and
speaks about the topic in relation to web development and Android development.
I’ve had the privilege to learn from Eevis as a colleague and a meetup
organizer and every time her new blog post appears in my RSS feed, I know I
have an opportunity to learn and become a better software developer.
</p>
Bloggers I read: Wednesday
2023-08-16T00:00:00Z
https://hamatti.org/posts/bloggers-i-read-wednesday/
<p>
I follow over a 100 tech blogs in my RSS reader actively and this week I’ve
picked ~25 of my favorites to share with you. The blogs are not in any
particular order neither by day nor within a single day. I started yesterday
with the first 5 and today is the second batch!
</p>
<h2 class="notion-heading_2 notion-color-default">Lea Verou</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-wednesday/1.png" alt=" " />
<p>
I first discovered
<a href="https://lea.verou.me/" class="notion-text-href">Lea</a> through her
talk
<a href="https://www.youtube.com/watch?v=3ikye7Qc7Ak" class="notion-text-href">“More CSS Secrets: Another 10 things you may not know about CSS” at W3Conf
2013</a>
and was truly blown away by her setup: slides including editable CSS that
changed examples on the slides themself.
</p>
<p>
Throughout these nearly 10 years, I’ve learned so much from her through her
CSS talks and her blog. Both about the technical content itself but also about
writing, presenting and building presentations.
</p>
<p>
Her work also powers this website as she’s the author of
<a href="https://prismjs.com/" class="notion-text-href">the code highlight library Prism.js</a>.
</p>
<h2 class="notion-heading_2 notion-color-default">Chris Coyier</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-wednesday/2.png" alt=" " />
<p>
<a href="https://chriscoyier.net/" class="notion-text-href">Chris</a> is the
co-founder of
<a href="https://codepen.io/" class="notion-text-href">CodePen</a>, a service
that I like a lot but don’t use nearly enough for my own good, and the
original author of
<a href="https://css-tricks.com/" class="notion-text-href">CSS-Tricks.com</a>
which has been instrumental in my web development learning journey throughout
years.
</p>
<p>I love Chris’s quote on his about page:</p>
<blockquote class="notion-quote notion-color-default">
Write the article you wish you found when you googled something.
</blockquote>
<p>That’s a mindset that I try to always remind myself of.</p>
<p>
Chris writes a near-daily blog about things in his life, including great tech
posts.
</p>
<h2 class="notion-heading_2 notion-color-default">Sara Soueidan</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-wednesday/3.png" alt=" " />
<p>
Whenever I want to become better at dealing with SVG, I find myself browsing
through
<a href="https://www.sarasoueidan.com/" class="notion-text-href">Sara’s website</a>, listening to her visits to podcasts or searching for her talks in Youtube.
</p>
<p>
She has
<a href="https://www.sarasoueidan.com/blog/" class="notion-text-href">a great technical blog</a>
where she focuses on accessible web, CSS and SVG. I’ve also found a lot of joy
and inspiration through her writings at
<a href="https://www.sarasoueidan.com/desk/" class="notion-text-href">The Desk</a>
which is for her non-technical writing.
</p>
<h2 class="notion-heading_2 notion-color-default">Lars Wikman</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-wednesday/4.png" alt=" " />
<p>
<a href="https://underjord.io/" class="notion-text-href">Lars</a> is one of
the most recent new additions to my RSS feed. He writes about Elixir, Erlang
and Phoenix and it’s been a great place for me to dive into a completely new
language ecosystem for me.
</p>
<p>
I originally found Lars through his
<a href="https://www.youtube.com/@underjord/" class="notion-text-href">Youtube channel</a>
where he has different types of videos from technical discussions to
vlog-style content.
</p>
<h2 class="notion-heading_2 notion-color-default">Stephanie Stimac</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-wednesday/5.png" alt=" " />
<p>
<a href="https://stephaniestimac.com/" class="notion-text-href">Stephanie</a>
is a designer and frontend developer and the author of
<a href="https://www.manning.com/books/design-for-developers" class="notion-text-href">Design for Developers book</a>
from who I’ve learned a lot about things that I’m at my weakest when it comes
to web development.
</p>
<p>
She’s (or at least was, I’m not sure how active this project is these days)
also involved in
<a href="https://webwewant.fyi/" class="notion-text-href">The Web We Want initiative</a>
that collects feedback about the current state of the web.
</p>
<p>
I also enjoy her non-technical blog posts because they often touch on topics
that I’m interested in or going through on some level and I like to read about
other people’s experiences on those.
</p>
<p></p>
Bloggers I read: Tuesday
2023-08-15T00:00:00Z
https://hamatti.org/posts/bloggers-i-read-tuesday/
<p>
I follow over a 100 tech blogs in my RSS reader actively and this week I’ve
picked ~25 of my favorites to share with you over the next 5 days. The blogs
are not in any particular order neither by day nor within a single day.
</p>
<h2 class="notion-heading_2 notion-color-default">Cassidy Williams</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-tuesday/1.png" alt="Screenshot of Cassidy Williams’s website at " />
<p>
Let’s start with Cassidy Wlliams who creates and shares a ton of great web
development related stuff. She has
<a href="https://dev.to/cassidoo" class="notion-text-href">a blog in DEV</a>
where she writes about web development, technology in general and has great
posts about static site generators.
</p>
<p>
In addition to that, Cassidy also runs
<a href="https://cassidoo.co/newsletter/" class="notion-text-href">a weekly newsletter</a>
where she shares links to great things in the web and has jokes!
</p>
<p>
You should also head out to her main page at
<a href="https://cassidoo.co/" class="notion-text-href">https://cassidoo.co/</a>
and hover your mouse over the links. That’s a true RGB experience for you!
</p>
<h2 class="notion-heading_2 notion-color-default">Alvin Bryan</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-tuesday/2.png" alt="Screenshot of Alvin Bryan’s website at alvin.codes " />
<p>
<a href="https://alvin.codes/" class="notion-text-href">Alvin</a> is your
friendly neighborhood developer advocate from Contentful. He has written
recently quite a few bits from the DevRel perspective that are great reading
for anyone interested in getting into that field but in general you can find
really well-written pieces about all sorts of things of life of a software
developer.
</p>
<h2 class="notion-heading_2 notion-color-default">Salma Alam-Naylor</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-tuesday/3.png" alt="Screenshot of Ssalma Alam-Naylor’s website at " />
<p>
<a href="https://whitep4nth3r.com/" class="notion-text-href">Salma</a> is a
developer educator who creates amazing stuff both in written and video format
about web development topics. She regularly
<a href="https://www.twitch.tv/whitep4nth3r/" class="notion-text-href">streams on Twitch</a>
and publishes blog posts that are very high quality, often step-by-step
documented tutorials for how to get things done.
</p>
<p>
For example, her
<a href="https://whitep4nth3r.com/blog/best-light-dark-mode-theme-toggle-javascript/" class="notion-text-href">tutorial to dark mode toggles</a>
was a bit inspiration as I have been building my own version for this website.
</p>
<h2 class="notion-heading_2 notion-color-default">Amos Wenger</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-tuesday/4.png" alt="Screenshot of Amos Wenger’s website at fasterthanli.me " />
<p>
I originally found Amos because of
<a href="https://fasterthanli.me/tags/advent-of-code" class="notion-text-href">his great Rust solutions and explanations of Advent of Code coding
puzzles</a>
when I was learning Rust. He writes interesting and well-written
<a href="https://fasterthanli.me/articles" class="notion-text-href">articles</a>
(mostly about Rust) and has some
<a href="https://fasterthanli.me/series" class="notion-text-href">longer-form serial content</a>
worthy of checking out.
</p>
<h2 class="notion-heading_2 notion-color-default">Mara Bos</h2>
<img src="https://hamatti.org/assets/img/posts/bloggers-i-read-tuesday/5.png" alt=" " />
<p>
<a href="https://blog.m-ou.se/" class="notion-text-href">Mara</a> is very
active member in the Rust community and one my favorite bloggers in general.
The style and quality of her writing is something I aspire to reach. She’s
excellent at explaining very technical details in an understandable way but
also she has the talent to mix it with fun and whimsical writing in a perfect
balance.
</p>
<p>
One of the great examples of that is a super in-depth technical exploration of
<a href="https://blog.m-ou.se/floats/" class="notion-text-href">Converting Integers to Floats Using Hyperfocus</a>
where you can find pieces like this:
</p>
<blockquote class="notion-quote notion-color-default">
While I was sitting there, struggling for hours
<i class="notion-text-italic">banging virtual rocks together</i> to light a
spark to make a campfire, Rustc was just sitting there with a lighter in its
pocket. “Hey you never asked,” Rustc laughed, “I figured you were just having
fun.”
</blockquote>
<p></p>
Pokemon tools updated for Obisidian Flames
2023-08-14T00:00:00Z
https://hamatti.org/posts/pokemon-tools-updated-for-obsidian-flames/
<img src="https://hamatti.org/assets/img/posts/pokemon-tools-updated-for-obsidian-flames/1.png" alt="Scarlet & Violet Obsidian Flames " />
<p>
Pokemon TCG’s newest set,
<a href="https://tcg.pokemon.com/en-us/expansions/obsidian-flames/" class="notion-text-href">Obisidian Flames</a>
was published last Friday, August 11th and for me, that means it was time to
update my Gym Leader Challenge Decklist Validator and Pokemon TCG Card Viewer
Firefox extension so trainers all around the globe are better equipped for the
new cards in the format.
</p>
<h2 class="notion-heading_2 notion-color-default">
Gym Leader Challenge Decklist Validator
</h2>
<img src="https://hamatti.org/assets/img/posts/pokemon-tools-updated-for-obsidian-flames/2.png" alt="Gym Leader Challenge Deck Validator " />
<p>
<a href="https://gymleaderchallenge.com/" class="notion-text-href">Gym Leader Challenge</a>
is the
<a href="https://hamatti.org/gaming/the-best-way-to-play-pokemon-tcg/" class="notion-text-href">best way to play Pokemon TCG</a>
these days. A format that was born from the community, masterminded by
<a href="https://www.youtube.com/c/TrickyGym" class="notion-text-href">Andrew Mahone</a>, fixes a lot of issues that standard format has and gives an opportunity for
new cards to shine.
</p>
<p>
As the decks are singleton, meaning you can only play one of each card
(excluding basic energies), there’s a lot of cards in a deck and mistakes can
slip in.
</p>
<img src="https://hamatti.org/assets/img/posts/pokemon-tools-updated-for-obsidian-flames/3.png" alt="Screenshot of GLC Decklist Validator with a valid fighting deck that passes all checks " />
<p>
<a href="https://glc-checker.netlify.app/" class="notion-text-href">Gym Leader Challenge Decklist Validator</a>
is a web tool that accepts Pokemon TCG Online or Pokemon TCG Live formatted
deck exports and checks for all the rules in deckbuilding for the format:
</p>
<ul class="notion-bulleted_list">
<li class="notion-bulleted_list_item notion-color-default">Monotype</li>
<li class="notion-bulleted_list_item notion-color-default">Singleton</li>
<li class="notion-bulleted_list_item notion-color-default">
No rulebox cards
</li>
<li class="notion-bulleted_list_item notion-color-default">
Juniper/Sycamore/Professor’s Research rule
</li>
<li class="notion-bulleted_list_item notion-color-default">
Lysandre/Boss’s Orders rule
</li>
<li class="notion-bulleted_list_item notion-color-default">Ban list</li>
<li class="notion-bulleted_list_item notion-color-default">Set viability</li>
</ul>
<img src="https://hamatti.org/assets/img/posts/pokemon-tools-updated-for-obsidian-flames/4.png" alt="Screenshot of fighting type GLC deck that fails monotype, singleton and rulebox checks " />
<h3 class="notion-heading_3 notion-color-default">Still building with hacks</h3>
<p>
Pokemon TCG Online was the official digital client to play Pokemon TCG for
years so a lot of tools are built on top of its export format. Pokemon TCG
Live came out recently and it has a similar export function but not all the
tools have updated to match that yet.
</p>
<p>
The API I use,
<a href="https://pokemontcg.io/" class="notion-text-href">pokemontcg.io</a>
has PTCGO codes for all cards that were available in that client but it does
not (at least yet) have support for Live codes.
</p>
<pre class="language-javascript"><code class="language-javascript">else if (setCode === "OBF") {
cards = await pokemon.card.all({ q: `set.id:sv3` });
cards = cards.map((card) => ({ ...card, ptcgoCode: "OBF" }));
}</code></pre>
<p>
Right now, my solution has been to add a new
<code class="notion-text-code">else if</code> clause to my code that instead
of passing the code as-is to API, searches with the id and then for my local
use, I add the <code class="notion-text-code">ptcgoCode</code> attribute to
the object so I can then later match directly from the decklist input to these
cards.
</p>
<p>
This is now the third set in the Scarlet & Violet series (the first series
that was no longer available in PTCGO) so I have three of these branches. It’s
still manageable but I might need to refactor the code after a while if the
API doesn’t add Live set codes to the data.
</p>
<h2 class="notion-heading_2 notion-color-default">
Pokemon TCG Card Viewer Firefox extension
</h2>
<img src="https://hamatti.org/assets/img/posts/pokemon-tools-updated-for-obsidian-flames/5.png" alt="Pokemon TCG Card Viewer " />
<p>
Similarly, I’ve updated the
<a href="https://addons.mozilla.org/en-GB/firefox/addon/pokemon-tcg-card-viewer/" class="notion-text-href">Pokemon TCG Card Viewer</a>
extension for Firefox to map the Live export code
<code class="notion-text-code">OBF</code> to a set with id of
<code class="notion-text-code">sv3</code>.
</p>
<p>
This extension lives on your browser’s toolbar and when clicked, will search
the page for all Pokemon TCG Online/Live export codes (like OBF 125 for the
new powerful Charizard ex) and wrap them all with
<code class="notion-text-code"><span></code>'s that when hovered, will
display the image of that card directly next to the mouse.
</p>
<p>
This saves players multiple trips to sites like
<a href="https://pkmncards.com/" class="notion-text-href">Pkmncards.com</a>
when they are browsing deck lists on Youtube, blog posts, articles or forums.
</p>
<p>
I have on my todo list to update this to the new Manifest V3 format and to
also make it available for Chromium based browsers so even more players can
access the power of the extension but for now, it’s advisable to switch to
<a href="https://www.mozilla.org/en-US/firefox/new/" class="notion-text-href">Firefox</a>
anyway if you’re using Chrome or Edge.
</p>
Desert Island Discs
2023-08-13T00:00:00Z
https://hamatti.org/posts/desert-island-discs/
<p>
<a href="https://www.bbc.co.uk/programmes/b006qnmr">Desert Island Discs</a> is
a fantastic, long-running (since 1942!) BBC radio show where they interview
people through a classic format of "what would you bring with you to a desert
island".
</p>
<p>
Each guest is asked to bring with them 8 songs, a book and a luxury item and
then during the program, they discuss what these things mean to the guest and
why they chose them. I find that very delightful and want to do my own
version.
</p>
<h2 id="-8-songs">💿 8 songs</h2>
<h3 id="tehosekoitin-syntynyt-k-yh-n-">
<a href="https://www.youtube.com/watch?v=6BeKEeOvhzU" target="_blank">Tehosekoitin - Syntynyt Köyhänä</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="ALbum cover for Tehosekoitin's Syntynyt Köyhänä" src="https://hamatti.org/assets/img/posts/desert-island-discs/syntynyt-koyhana.jpg" />
<p>
When I was a kid, there were three bands I listened more than others – a
preference that I got through my brother and sister. Tehosekoitin, Klamydia
and Apulanta. All three have a special place in my music heart and it's hard
to pick a favorite out of the bunch but for this trip, I'm picking
Tehosekoitin's Syntynyt Köyhänä (in English: Born Poor).
</p>
<p>
While we were never really poor, my childhood was definitely colored by
<a href="https://en.wikipedia.org/wiki/Early_1990s_depression_in_Finland">the 90s depression in Finland</a>
and as a family of three kids, we had to make do with what we had. That's
likely why this song has always been close to my heart, in addition to it just
being a banger good song.
</p>
<h3 id="abba-waterloo">
<a href="https://www.youtube.com/watch?v=Sj_9CiNkkn4" target="_blank">ABBA - Waterloo</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="ALbum cover for ABBA's Waterloo" src="https://hamatti.org/assets/img/posts/desert-island-discs/waterloo.jpg" />
<p>
I love ABBA. It's amazing how few bad or even meh songs they have produced
over the years. Nearly every one of their songs is great but my all time
favorite is Waterloo. Also, I love
<a href="https://www.youtube.com/watch?v=IQDJyKmqej8">this scene from Mamma Mia! Here We Go Again</a>
with the song.
</p>
<p>
The song is so upbeat that it's impossible to not start dancing whenever I
hear it - a mood much needed during the long days at the island alone.
</p>
<h3 id="cmx-punainen-komentaja">
<a href="https://www.youtube.com/watch?v=LdEX_q31_-g" target="_blank">CMX - Punainen Komentaja</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="ALbum cover for CMX's Talvikuningas" src="https://hamatti.org/assets/img/posts/desert-island-discs/talvikuningas.jpg" />
<p>
Some people are of an opinion that a good album is a singular piece of art
that should be enjoyed in its entirety instead of picking individual songs.
I've never been that person and most of my listening is random songs here and
there through apps like Spotify and Youtube Music.
</p>
<p>
However, there's one exception: CMX's Talvikuningas. It's a scifi themed album
with songs that tell stories from that universe. And the top song is Punainen
Komentaja (Red Commander) that tells a story of a Commander Hiram, a
well-versed space commander who has seen a lot throughout their years.
</p>
<p>
There's also a lovely
<a href="https://youtu.be/CNkBeuKbrY8">fan-made cover that fits Santa as the Red Commander</a>.
</p>
<h3 id="bonnie-tyler-holding-out-for-a-hero">
<a href="https://www.youtube.com/watch?v=bWcASV2sey0" target="_blank">
Bonnie Tyler - Holding Out for a Hero
</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="ALbum cover for Bonnie Tyler's Secret Dreams and Forbidden Fire" src="https://hamatti.org/assets/img/posts/desert-island-discs/secret-dreams-and-forbidden-fire.jpg" />
<p>
Bonnie Tyler's Holding Out for a Hero is a <strong>banger.</strong> It's one
of those songs that just hits right. Whether its playing on the background of
epic scenes in
<a href="https://www.youtube.com/watch?v=A_HjMIjzyMU">Shrek</a> or
<a href="https://www.youtube.com/watch?v=81wyj65SJIo">He-Man Masters of the Universe</a>, it builds up from slow start into a explosive heroic tunes.
</p>
<p>
It's one of my power songs: the kind of song that I listen when I need to get
myself pumped up and ready for something, for example before getting on a
stage to speak.
</p>
<h3 id="leevi-and-the-leavings-koko-talvi-kes-m-kill-">
<a href="https://www.youtube.com/watch?v=kV4rDPlmzDc" target="_blank">
Leevi and the Leavings - Koko talvi kesämökillä
</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="ALbum cover for Leevi and the Leavings' Rakkauden planeetta" src="https://hamatti.org/assets/img/posts/desert-island-discs/rakkauden-planeetta.jpg" />
<p>
Gösta Sundqvist, the lead singer and songmaker in Leevi and the Leavings, is
undoubtedly one of the greatest songmakers in Finnish music history. Just like
with ABBA, it's hard to pick one song from the great catalogue of their
creations but Koko talvi kesämökillä (Entire winter at the summer cottage) is
a nice, moody song that fits this collection to bring in some of that Finnish
moodyness to the mix.
</p>
<p>
If you understand Finnish, I can highly also recommend
<strong><a href="https://areena.yle.fi/podcastit/1-4447708">Itkisitkö onnesta? Tarina Gösta Sundqvistista</a></strong><a href="https://areena.yle.fi/podcastit/1-4447708"> audio play</a> that is a
biography of Gösta's life.
</p>
<h3 id="bob-dylan-the-times-they-are-a-changin-">
<a href="https://www.youtube.com/watch?v=90WD_ats6eE" target="_blank">
Bob Dylan - The Times They Are A-Changin'
</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="Almvbum cover for Bob Dylan's The Times They Are A-Changin'" src="https://hamatti.org/assets/img/posts/desert-island-discs/the-times-they-are-a-changing.jpg" />
<p>
I'd say my true formative years were in my mid-20s when I started to discover
who I am and gained the courage to be that person. I moved to San Francisco to
work in a startup, learned a ton, gained a lot of self-confidence and it
opened up a whole new world for me.
</p>
<p>
During that time, I listened to a lot of Bob Dylan and especially this song,
The Times They Are A-Changin' which is a brilliant piece of music and lyrics.
It's one of those songs for me that when I want to remember back those days, I
put it on, sit on the couch or lie in the bed and just listen to the beautiful
melody and impactful lyrics of Dylan's masterpiece.
</p>
<h3 id="taylor-swift-blank-space">
<a href="https://www.youtube.com/watch?v=e-ORhEE9VVg" target="_blank">
Taylor Swift - Blank Space
</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="Almvbum cover for Taylor Swift's 1989" src="https://hamatti.org/assets/img/posts/desert-island-discs/1989.png" />
<p>
I'm a recent Taylor Swift fan but the more I listen to her songs, the more I
like about them. Disney+ has this great Taylor Swift piece
<a href="https://www.disneyplus.com/movies/folklore-the-long-pond-studio-sessions/3Xlc0EjKtKpp">folklore: the long pond studio sessions</a>
where she plays songs from her folklore album while also discussing the
creation and meaning of the songs. Another favorite of mine is
<a href="https://www.youtube.com/watch?v=FvVnP8G6ITs">her NPR Tiny Desk Concert</a>.
</p>
<p>
The first time I heard about Blank Space was through
<a href="https://www.youtube.com/watch?v=CFX8VJUtDgY">Imagine Dragons' cover in BBC Live Lounge</a>. I fell in love with the song right there and enjoy both versions a ton.
</p>
<p>
Swift is the type of artist that I could just listen all day long and feel
happy about things.
</p>
<h3 id="billy-joel-piano-man">
<a href="https://www.youtube.com/watch?v=QwVjTlTdIDQ" target="_blank">
Billy Joel - Piano Man
</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="Almvbum cover for Billy Joel's Piano Man" src="https://hamatti.org/assets/img/posts/desert-island-discs/piano-man.webp" />
<p>
Another more moody selection to balance out the set is Billy Joel's Piano Man.
There's just something beautiful about the melody and the lyrics to this song.
It was possibly one of my first LPs I owned and one that I've listened the
most.
</p>
<p>
It's perfect music for those rainy days, sitting on the porch or balcony,
listening to rain drops hit the roof with Piano Man playing through speakers.
I just stare into the distance and take in all the feelings that come from it.
</p>
<h3 id="a-bonus-pick-when-nobody-looks-david-bowie-starman">
<a href="https://www.youtube.com/watch?v=aBKEt3MhNMM" target="_blank">
A bonus pick when nobody looks: David Bowie - Starman
</a>
</h3>
<img style="max-width: 300px; margin-bottom: 1em" alt="Almvbum cover for David Bowie's The Rise and Fall of Ziggy Stardust and the Spiders from Mars" src="https://hamatti.org/assets/img/posts/desert-island-discs/ziggy-stardust.webp" />
<p>
Is it even a list of N items if you don't sneak in an extra one unasked? David
Bowie's Starman is a powerful story and part of a great album.
</p>
<blockquote>
There's a Starman waiting in the sky<br />He'd like to come and meet us<br />But
he thinks he'd blow our minds<br />There's a Starman waiting in the sky<br />He's
told us not to blow it<br />'Cause he knows it's all worthwhile
</blockquote>
<h2 id="-book-douglas-adams-the-hitchhiker-s-guide-to-the-galaxy">
📚 Book: Douglas Adams - The Hitchhiker's Guide to the Galaxy
</h2>
<p>
This one is an easy pick. My all time favorite 5-book trilogy by one of the
greatest authors who ever lived.
<a href="https://en.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy">The Hitchhiker's Guide to the Galaxy</a>
is a masterpiece in most of its formats: the book series, the original radio
play, the Finnish radio play adaptation by Radioteatteri (which I mostly
remember word-by-word as I've listened it through dozens of times).
</p>
<p>
The story is so captivating, quirky, weird and full of amazing characters that
even though I know what happens, I'm drawn back to it time after time. I used
to own this faux-leather covered book version that had all the five original
books and a few short stories before I gave it forward when digitizing my book
collection to ebooks.
</p>
<p>
Just as Arthur Dent gets pulled into an adventure far away from home as Earth
is destroyed to make way for an intergalactic highway, I feel like ending up
on a desert island would be a similar adventure and just as The Hitchhiker's
Guide to the Galaxy (the in-universe encyclopedia) is there to provide
information and comfort for the intergalactic traveler, The Hitchhiker's Guide
to the Galaxy (the real world book) could offer that same comfort for me all
alone in an island.
</p>
<h2 id="-luxury-item-steam-deck-solar-powered-charger-">
💎 Luxury item: Steam Deck (+ solar powered charger)
</h2>
<p>
Everyone who has a Steam library of games probably knows that we all have such
a large backlog of games we've bought from sales and Humble Bundles and such
that we've never played. When would be a better time to play through all of
them than being deserted on an island? And with the added solar powered
charger, I could be playing all day long through my never-ending backlog.
</p>
<p>
On a more serious note though,
<a href="https://store.steampowered.com/steamdeck/">Steam Deck</a> is a
marvellous piece of technology. I got mine last fall and it has completely
changed how I game. I also have PS4 and Switch but the amount of time spent
playing those dropped significantly when I got the Steam Deck. It's (almost*)
all I hoped Switch would be.
<em>(* the only exception being Nintendo's 1st party exclusives like Mario,
Zelda and Pokemon franchises)</em>
</p>
<p>
Steam Deck a versatile gaming beast with its multiple modes of gaming: short
sessions while on the go, longer hand-held adventures at home or at hotels,
big screen gaming at home connected to a projector and desktop gaming with a
display, keyboard and mouse. And due to it being standard PC, you don't need
to buy proprietary anything to it. You can use your regular mouse, keyboard,
USB-C dongles, USB-C chargers and can even boot to a Linux desktop environment
to be productive.
</p>
<p>
Given Valve's not-so-successful previous attempts with things like Steam Link
and Steam Controller, I was sceptical of Deck but it turned out to be
brilliant. I can't overemphasize how great this device is. And combined with
Steam's incredible game library (and the ability to run other launchers like
Epic Games and GOG), there's so much to play.
</p>
<p>
Some rapid fire game recommendations that are amazing on Deck since we're
here: <a href="https://store.steampowered.com/app/1562430/DREDGE/">DREDGE</a>,
<a href="https://store.steampowered.com/app/460950/Katana_ZERO/">Katana ZERO</a>,
<a href="https://store.steampowered.com/app/646570/Slay_the_Spire/">Slay the Spire</a>,
<a href="https://store.steampowered.com/app/413150/Stardew_Valley/">Stardew Valley</a>
(with keyboard & mouse),
<a href="https://store.steampowered.com/app/205100/Dishonored/">Dishonored</a>,
<a href="https://store.steampowered.com/app/1455840/Dorfromantik/">Dorfromantik</a>,
<a href="https://store.steampowered.com/app/1970580/Backpack_Hero/">Backpack Hero</a>,
<a href="https://store.steampowered.com/app/1637320/Dome_Keeper/">Dome Keeper</a>,
<a href="https://store.steampowered.com/app/1054490/Wingspan/">Wingspan</a>,
<a href="https://store.steampowered.com/app/383870/Firewatch/">Firewatch</a>,
<a href="https://store.steampowered.com/app/287980/Mini_Metro/">Mini Metro</a>
&
<a href="https://store.steampowered.com/app/1127500/Mini_Motorways/">Mini Motorways</a>
and
<a href="https://store.steampowered.com/app/239030/Papers_Please/">Papers, Please</a>. And all the great NES, SNES, N64 and GameCube games through emulation.
</p>
<h2 id="what-are-your-8-records-book-and-luxury-item">
What are your 8 records, book and luxury item?
</h2>
<p>
I'd love to hear what you'd bring! You can comment on this post by replying to
this blog post in Mastodon via links below in comments section.
</p>
Future Frontend 2023 Recap
2023-08-12T00:00:00Z
https://hamatti.org/posts/future-frontend-2023-recap/
<p>It's been a while since my last conference, <a href="https://hamatti.org/posts/devrelcon-prague-2022-recap/">DevRelCon in Prague in December</a>. Earlier this summer, we organized <a href="https://futurefrontend.com/">Future Frontend</a> in Helsinki to gather together developers building or interested in the new things that frontend ecosystem is going through. The conference is a continuation from <a href="https://hamatti.org/posts/react-finland-2022-recap/">our previous React Finland conferences</a> as we decided to pivot the topic from React to a wider perspective.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img alt="Future Frontend logo on a large screen at a stage with a woman and man standing on opposite sides of the stage, speaking to the audience." src="https://hamatti.org/assets/img/ghost//2023/06/52968773058_3a947ba462_c.jpg" class="kg-image" /><figcaption>Photo by Future Frontend (CC BY 2.0)</figcaption></figure><p>One of the cool things we did this year was to have talks in sessions of 2 or 3 talks and then a combined Q&A / short panel discussion with the MC and speakers at the end. It really brought together the concepts discussed and provided the audience a good opportunity to dig deeper with questions.</p><p>Here are my picks for the talks that I, from a participant point of view, enjoyed the most – checking out the rest will be left for homework to you! You can find all the talks from <a href="https://www.youtube.com/playlist?list=PLKGgD1M40S4gyWKnqW1SPg1pWMfvZcmLr">our Youtube channel</a>.</p><p><strong>Creating universal design systems across web and mobile with Utility First Styling by Mohammad Khazali</strong></p><p><strong>Establishing the rules for a universal design system by Thaís Santos</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img alt="Mohammad on stage talking" src="https://hamatti.org/assets/img/ghost//2023/06/52974316974_7173c6fc60_c.jpg" class="kg-image" /><figcaption>Photo by Future Frontend (CC BY 2.0)</figcaption></figure><p>This double session on the first day of the conference discussed universal design systems from two perspectives. First,<a href="https://www.youtube.com/watch?v=t7ptxuojuYA"> Mohammad Khazali introduced the the topic from developer's perspective</a> and in the second talk, <a href="https://twitter.com/th4is_ds">Thaís Santos</a> took <a href="https://www.youtube.com/watch?v=UtHDWYmDjGM">a look at the topic from more of a designer perspective</a>.</p><p>Universal Design Systems are design systems that aim to provide a set of design tokens, components and guidelines for building across different devices: web, iOS and Android.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/06/52974166931_8ef57b0470_c.jpg" alt="Thaís standing on a stage with a slide on the background that reads '25% of popular design systems include mobile components'" class="kg-image" /><figcaption>Photo by Future Frontend (CC BY 2.0)</figcaption></figure><p>One thing that really resonated with me was one of the latter points in Thaís' talk about how just having tokens and components is more of a library but adding documentation and guidelines for why and how and when to use them and how to combine them, is what makes them an efficient and enjoyable design system to use. That applies to so many things in software development and it was great to see it brought up in this talk as well.</p><p><strong>Adaptive Music for the Web With RNBO by Tero Parviainen</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img alt="Tero standing behind a speaker booth on the stage with a slide barely visible on the background showing lots of slider toggles. On the foreground, there's a bunch of people sitting in the conference hall, listening to Tero speak." src="https://hamatti.org/assets/img/ghost//2023/06/52974170861_88bc9e1d89_c-1.jpg" class="kg-image" /><figcaption>Photo by Future Frontend (CC BY 2.0)</figcaption></figure><p>One of the talks from the first day that sparked a ton of great discussion was <a href="https://www.youtube.com/watch?v=dL_XHIKaWnI">Tero's session</a> about adaptive music with <a href="https://cycling74.com/products/rnbo">RNBO</a>.</p><blockquote>I do random shit and then notice when something is amazing and continue doing that.</blockquote><p>Tero introduced the concept and a few web projects in the start but spent most of the talk doing a live demo, building an ambience sound generator that listens to sounds around it and generates music based on that.</p><p>It's always amazing to see these kind of live demos, especially audio ones where there's a constant stream of audio and by adding nodes changes it live as the demo progresses.</p><p><strong>Everything's better with friends by Sunil Pai</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img alt="Sunil on the stage with a slide that reads 'Everything's better with friends'." src="https://hamatti.org/assets/img/ghost//2023/06/52968492714_3c48c4cbf7_c.jpg" class="kg-image" /><figcaption>Photo by Future Frontend (CC BY 2.0)</figcaption></figure><p><a href="https://www.youtube.com/watch?v=auFkJ2k28f4">Sunil's presentation about the future of Internet being multiplayer</a> was a blast! It was a great combination between fun and highly entertaining presentation with great technical content. He talked about the challenges and opportunities in building collaborative applications on the web.</p><p>Sunil has built <a href="https://partykit.io/">Party Kit</a>, <em>an open source deployment platform for AI agents, multiplayer and local-first apps, games, and websites</em>, which he opened up for public use during the talk.</p><p><strong>Hacking meaningful connections with humans by talking to (toy) rodents by Stephanie Nemeth</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img alt="Stephanie on a speaker booth with a very colorful pompom and two small hamster plushies on a high table next to her." src="https://hamatti.org/assets/img/ghost//2023/06/52973572287_1af9b1791d_c.jpg" class="kg-image" /><figcaption>Photo by Future Frontend (CC BY 2.0)</figcaption></figure><p>The best was left for the last! The talk I was most waiting to see was <a href="https://www.youtube.com/watch?v=f8XpeszsRLI">Stephanie Nemeth's brilliant talk</a>. She had built a giant pom pom that generated art into an eInk screen when hugged and two hamster plushies that talked: when you hold one's paw and talk to them, the other hamster gets all excited, starts to shake and repeats what you said in a high pitch hamster voice.</p><p>I love these kind of projects and the showcases that people bring to meetups and conferences. Experimentation, learning new things, tinkering and in general, creating happiness and joy into the world are all great things.</p><h2 id="thanks-">Thanks!</h2><p>Big thanks to our team of organizers: Juho, Tuuli, Eemeli, Toni, Harri and Aleksi for the great work, to our volunteers Jeanne, Joonas, Josefina, Ari and Clément for helping us make the event a reality, to our sponsors Gofore, Elisa, Knowit and Alma Media for enabling us to bring this community together, to all the fantastic speakers who joined our event to share what they are working on and of course all the participants for being part of this community.</p><p>We're already put the wheels in motion for 2024 event and I can't wait for the event.</p><h2 id="other-people-s-recaps">Other people's recaps</h2><p>If you've shared your experiences of the conference, let me know (for example in conference Slack (@juhis) or in Mastodon (@hamatti@hamatti.org)) and I'll add it to the list.</p><ul><li><a href="https://blog.knowit.fi/efficiency-in-the-frontline-key-takeaways-from-future-frontend-2023">Efficiency in the Frontline: Key Takeaways from Future Frontend 2023 by Aki Rönnkvist / Knowit</a></li></ul>
Projects I'm proud of: Teaching programming
2023-08-11T00:00:00Z
https://hamatti.org/posts/projects-im-proud-of-teaching-programming/
<p><em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a> is a month-long event that takes place in August each year that focused on blogging and other serialized content. The goal is to stoke the fires of creativity and allow bloggers and other content creators to mingle in a shared community while pushing each other to post more regularly.</em></p><p><em>This week's theme is <strong>Introduce yourself</strong></em><strong> </strong><em>and I decided to do that through sharing a few projects that I've done since I think these stories will share quite a bit about who I am as a human being as well.</em></p><p>Teaching programming has been one of my biggest passion projects for over a decade. Learning how to program myself was a rough ride that I'm still riding and when I started university for the first time, I didn't have anyone to study with or help out. After I moved to another university, started over again and got a chance to join all the first year student activities to make friends, I already knew a bit more about programming. </p><p>I decided that my new friends wouldn't have to struggle the same way I did so we started doing peer-learning sessions to learn together. On my second year, I became a student mentor in a new program where the faculty recruited a couple of us students to host extra learning sessions. That lead to me becoming a teaching assistant, running demo sessions (you might have called them labs or homework) and supervising project work.</p><h2 id="in-person-teaching">In-person teaching</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/07/rails_girls_coaching_santala_sm.jpg" class="kg-image" /><figcaption>Me at my first Rails Girls event in November 2013 - using white board that became my signature move. </figcaption></figure><p>After I had gotten my feet into the teaching world, I was sold by the idea. However, the real realization happened in November of 2013 when I joined my first <a href="https://railsgirls.com/">Rails Girls workshop</a> in Helsinki as a coach. I was very unsure about applying to be a coach but luckily I did and found a lovely and welcoming community of organizers, coaches and students.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/08/DSC_5315.jpg" class="kg-image" alt="A group of people, mostly women, listening to a speaker in a room" /><figcaption>We organized Rails Girls Turku in 2015</figcaption></figure><p>I did a lot with Rails Girls in the years to come. Right after my first one, I joined a chapter in San Francisco to coach there in the beginning of 2014. After returning to Finland, I took Rails Girls to a couple of new cities, organizing events and building communities in Turku, Salo and Jyväskylä.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/08/image.png" class="kg-image" alt="A group photo taken outside on a concrete yard. A group of people standing close to each other, all showing a heart sign with their hands." /><figcaption>We took Rails Girls to Jyväskylä in 2015</figcaption></figure><p>I still remember many of the students who attended these events and especially those who are now senior software developers building great careers and I feel so happy I had a role to play getting them excited about software development.</p><p>In addition to other individual events where I coached in, like <a href="https://clojurebridge.fi/">ClojureBridge</a>, <a href="https://djangogirls.org/en/">Django Girls</a> and reacTour, I truly found a new teaching home in <a href="https://codebar.io/">codebar</a> when it arrived to Finland in summer of 2018. I've hosted and sponsored their workshops, coached in a lot of workshops in Helsinki and Berlin and <a href="https://medium.com/the-codelog/coaching-at-codebar-has-given-me-a-lot-81b58664492">wrote about my experiences in their blog</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/08/20171006_200045.jpg" class="kg-image" alt="A group of people, all hugging the air towards the camera" /><figcaption>I was one of the coaches in Django Girls Helsinki, 2017</figcaption></figure><p>I mentioned it in the Boost Turku post earlier this week but during the summers I worked there as a community manager, we ran two differently styled Summer of Programming programs that I led.</p><p>The first year I gathered together a few of my coach friends from Rails Girls and other workshops to join as mentors, negotiated a partnership with CodeSchool to get free study content to students and hosted a summer-long course where each coach had a small group of students and we hosted in-person sessions every now and then.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/08/20170613_192158.jpg" class="kg-image" alt="A room full of people with their laptops, some coding on their own, others discussing in small groups" /><figcaption>Second summer of Boost Turku & Digit Summer of Code, 2017</figcaption></figure><p>The second year we partnered up with a university engineering student organization, opened the doors, did introductory presentations about HTML, CSS and Javascript and saw almost 100 people join the first session as we scrambled to get enough chairs to fit everyone in.</p><p>Every time, I've made new friends, helped people learn new concepts and kept learning more and more myself as well. I've given <a href="https://hamatti.org/talks/i-teach-therefore-i-learn/">some talks in events about my experience in becoming a better developer through teaching</a>.</p><p>Lately, I also had an opportunity to participate in redesigning a university course and lecturing there a couple of years bringing more practical approach to learning development than what academic institutions usually offer.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/08/image-1.png" class="kg-image" alt="A group of people sitting around tables with their laptops, listening to me speak in front of them in the small auditorium space" /><figcaption>ClojureBridge in Helsinki, 2019</figcaption></figure><h2 id="writing-technical-content">Writing technical content</h2><p>I love in-person teaching but there are limitations to how much of that can be done and how many opportunities there are.</p><p>To maximize the impact, I've done more teaching through writing technical blog posts and programming guides. You can find a lot in this blog and some of my current larger projects are <a href="https://hamatti.org/guides/humane-guide-to-debugging/">Humane Guide to Debugging Web Apps</a> and <a href="https://hamatti.org/guides/humane-guide-to-python-errors/">Humane Guide to Python Errors</a> and I'm currently working on three other bigger technical writing projects.</p><p>One of my dreams is to write a paid online course that would let me earn part of my income through subscribership. Right now I'm working really hard towards that: becoming a better writer, learning marketing and audience building and finding ways to provide enough value for developers to pay for my content.</p><h2 id="mentoring">Mentoring</h2><p>I've been mentoring junior developers also individually over the years, sometimes more structurally and sometimes more free flowing and I'm still looking for a great model to run a mentoring program that would involve other people.</p><p>I think there's so much senior developers could offer to juniors: not only in technical prowess but career coaching, connections to industry, taking them to events and helping people find their place in the industry.</p><p>Some companies offer mentoring programs and they can be good but I think it's also valuable to have a mentor that's from outside your organization as that allows more honest ponderings about future and dreams and goals. Sometimes the best option is out there.</p><p>I started a new mentoring relationship this week after not having taken new mentees in a while and I'm so excited. We had a great kickoff call to get to know each other a bit more and figuring out the first steps.</p><h2 id="the-upside">The upside</h2><p>One of the best feelings one can have is when someone reaches out to let me know that something I've written or taught has helped them or had a positive impact. I'm very proud that the amount of people who have become professional software developers partially inspired by my work is in high dozens. </p><p><strong>That's the true 10x impact to me. <a href="https://hamatti.org/posts/why-i-do-what-i-do/">That's also why I do what I do.</a></strong></p>
Projects I'm proud of: Potluck
2023-08-10T00:00:00Z
https://hamatti.org/posts/projects-im-proud-of-potluck/
<p><em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a> is a month-long event that takes place in August each year that focused on blogging and other serialized content. The goal is to stoke the fires of creativity and allow bloggers and other content creators to mingle in a shared community while pushing each other to post more regularly.</em></p><p><em>This week's theme is <strong>Introduce yourself</strong></em><strong> </strong><em>and I decided to do that through sharing a few projects that I've done since I think these stories will share quite a bit about who I am as a human being as well.</em></p><p>When I was a kid, I sucked at drawing and painting and all those arts and crafts. That led me to think of myself as a non-creative throughout my childhood well into my early adulthood. Only once I realized that I can be creative through programming as well as non-fictional, non-prose writing, I started to consider myself a creative. That opened up a lot of mental doors for me.</p><p>Graphic design has still always been the thing that's been eluding me. I've tried to learn, I've studied and practiced and even through necessity of building communities, crafted a lot of event posters and similar graphics.</p><p>Rather lately, I decided I wanted to learn the type of graphic and information design that would be beneficial to me when creating cards and tokens for tabletop games.</p><h2 id="minimal-travel-table-top-collections">Minimal Travel Table Top Collections</h2><p>It actually started in 2019 as I was traveling quite a bit and wanted to play board games when I was traveling. I had made so many great friends through board games and generally loved the hobby.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/IMG_20200203_165118__01-1.jpg" class="kg-image" /></figure><p>Thus, I built my first <a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">Minimal Travel Table Top Game Collection</a> that fitted a few existing board games into a deck of cards. I basically took existing designs and retrofitted or recreated them to fit individual cards. It fit into my backpack so I could take it anywhere on my travels.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-17.png" class="kg-image" /></figure><p>Only, the pandemic disagreed on the travel part. For <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/">Minimal Travel Table Top Game Collection 2: Social Distancing Edition</a> I took hobbyist-made print-and-play published solo games from BoardGameGeek's forums and created a second set that I could play on my own while stuck at home. Design-wise, this was the least effort as the games were already designed to be printed and played on standard sized cards.</p><p>Then I dove deep into research looking into universal game systems. The idea that you have a set of cards, tokens or pieces that are generic enough so they can play dozens, even hundreds of games got me interested. The more I read and researched, the more excited I got.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-16.png" class="kg-image" /></figure><p><a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Minimal Travel Table Top Game Collection 3: Project 108</a> had the worst name but was the most ambitious of my projects in this domain. It was designed to play a small handful of card games I really enjoyed playing while still having elements that were generic enough that they could be used for much more.</p><p>It was good. The world opened up, I got to travel a bit more and made new friends teaching and playing various games with my self-designed cards. But like all first iterations, there were things I wasn't particularly happy about and things that I noticed were useless or a hinderance to gaming. So it was time for a revision.</p><h2 id="minimal-travel-table-top-game-collection-iv-potluck">Minimal Travel Table Top Game Collection IV: Potluck</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-18.png" class="kg-image" /></figure><p>It was New Year's Eve. I was traveling to see my family and had to stay an extra day as there was no boat going back to Germany. I was sitting in a hotel room and decided, now is the time to start working on the fourth collection.</p><p>I named it Potluck as I find it a lovely concept: each part of the cards contribute to a larger, diverse collection of games that can be played.</p><p><strong><a href="https://hamatti.org/tabletop/potluck/">Potluck</a></strong> is a semi-universal deck that fits into a pocket and plays dozens (actually, at least a good few hundred) of different games.</p><p>I took Potluck way more seriously than I had my previous iterations. I wanted the product to look professional and like a real product someone might buy from a game store shelf or online.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-19.png" class="kg-image" /></figure><p>I made a few different "posters" like above showcasing different aspects of the cards and I really like how the general brand with dark blue background, faded out game card and D10 and the balance in typography turned out.</p><p>I improved on a good handful of features from the previous version: I changed the main font to something where 6 and 9 were distinquished in any rotation, I added running numbering to all cards to top corner, added another 4 suits of traditional cards and cleaned up the presentation.</p><p>I also added a ton of games I had learned to enjoy in the between, making the cards more usable and more feature-rich.</p><p>I learned a ton about <a href="https://hamatti.org/posts/potluck-my-workflow/">tooling to use</a> to make the process of creating these cards better. The techie in my was excited about that but I wanted to make this project more about learning design and productizing.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-20.png" class="kg-image" /></figure><p>I've never been as proud of something physical and tangible I've made than when I got my hands on the finished product last May. </p><p>It plays really well and is the current peak of my research and skills when it comes to tabletop products.</p><p>My dream is that one day eventually, I'll be able to replace this proud project with a tabletop game that I've designed myself from ground up, including the game design. I'm currently doing a lot of reading and studying from game designers' design stories (I subscribe to <a href="https://boardgamegeek.com/blogcategory/19/designer-diaries">Board Game Geek's Design Diaries</a>), existing games and doing small experiments around the information design of cards.</p>
Projects I'm proud of: Newsletters
2023-08-09T00:00:00Z
https://hamatti.org/posts/projects-im-proud-of-newsletters/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p>
<em>This week's theme is <strong>Introduce yourself</strong></em><strong> </strong><em>and I decided to do that through sharing a few projects that I've done
since I think these stories will share quite a bit about who I am as a human
being as well.</em>
</p>
<p><strong>Today is a two-for-one special Wednesday deal!</strong></p>
<p>
I'm always experimenting with new things, especially when it comes to telling
stories and sharing inspiration and knowledge. Today I'll share stories of two
developer newsletters I've been involved in running.
</p>
<h2 id="dev-breakfast-by-futurice">Dev Breakfast by Futurice</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2023/07/dev-breakfast.png" class="kg-image" />
</figure>
<p>
I joined software consultancy Futurice in 2018 and transferred into a
full-time Developer Advocate position in the summer of 2019. Even before I
started in the role, I'd been pitching an idea for starting a developer
newsletter.
</p>
<p>
Luckily, I had a great partner in our marketing team who shared my idea and in
August of 2019 we kicked off
<a href="https://futurice.com/devbreakfast">Dev Breakfast</a>, a monthly
newsletter from developers to developers.
</p>
<p>
It was a recruitment marketing tool for us in the company but I wanted it to
be something that any developer could enjoy and share even if they never were
interested in joining us or hearing about us. From the recruitment marketing
perspective, I had two leading principles:
</p>
<ol>
<li>
I wanted to build a high-quality newsletter that would land on the email
inboxes of developers once a month, keeping our name connected with quality
regularly in their minds so that when they'd start thinking about switching
jobs, they'd remember we exist and have positive thoughts about us.
</li>
<li>
I wanted to build a way for us to stay in touch with developers between
touchpoints. We'd meet them in meetups, student events, recruitment fairs,
conferences, project work and other places. By inviting them to subscribe to
the newsletter, we'd have a way to stay in touch with them between these
touchpoints.
</li>
</ol>
<p>
In the beginning, the first few months I was creating the content and the
marketing team was taking care of the operations. It worked well but I
realized rather quickly that what I had to share was just one man's opinion,
based on one man's interests and that probably wouldn't be that interesting to
a large and diverse audience for long.
</p>
<p>
The smartest move I made was to pivot the content curation. Instead of me, I
recruited developers from our company - across different tech interests and
across different offices around Europe - to do the curation. This lead to each
month's issue showcasing a different developer and bunch of articles, posts,
videos and tools from the Internet they found interesting. Diversifying the
authorship helped grow a bigger and more engaged audience.
</p>
<p>
<em>Sidenote: I personally loved reading all the newsletters as I got to learn
a lot every month both about the people I worked with as well as the
technical topics they cared about.</em>
</p>
<p>
Even after I left the company, I'm still a happy reader and wait each month
for the newest newsletter issue to arrive in my inbox.
</p>
<p>
One sign for me that it was a success was when the company decided to branch
out and start a
<a href="https://futurice.com/design-breakfast">Design Breakfast</a>
newsletter following the same formula but for designer audience. The more
important sign was when people would tell me that they really enjoyed the
newsletter and were happy subscribers.
</p>
<p>
I ran the content side of the newsletter from its inception in 2019 to when I
left the company in 2022, finishing with full 36 months, a nice moment to pass
the torch to the next person.
</p>
<p>
<em>If you're a developer who wants to learn and get inspired by other
developers, I recommend heading over to
<a href="https://futurice.com/devbreakfast">https://futurice.com/devbreakfast</a>
and signing up!
</em>
</p>
<h2 id="syntax-error-by-myself">Syntax Error by myself</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-21.png" class="kg-image" />
</figure>
<p>
At the end of 2022, I knew I had to
<a href="https://hamatti.org/posts/im-leaving-mozilla/">take a break from my work</a>
to focus on my physical and mental health. Like in 2015, when I switched from
software development to community management and wanted to stay on top of
things and started a tech meetup, I was in a similar situation this time.
</p>
<p>
I knew that this break from developer relations would be a temporary one so I
wanted to keep running a couple of projects I call "lifeboat projects".
Projects that if and when I'd be ready to return to developer relations, I'd
have something to show.
</p>
<p>
<a href="https://www.syntaxerror.tech/"><strong>Syntax Error</strong></a> was
born in a hotel room in Prague in December of 2022. (If you're following these
stories, you notice that a lot of projects are started in hotel rooms...)
After a rough patch at work, I was attending DevRelCon and got so much
inspiration I had to channel it somewhere productive.
</p>
<p>
So I sat down, created the concept, signed up for Ghost account and started
writing.
</p>
<p>
For years, I've been helping developers debug their code. Whether it's been
senior developers at work or juniors early in their career through teaching
and mentoring, I've seen similar patterns in people's approaches and wanted to
help them become better at it.
</p>
<p>
So I had a lot of stories and a knack for writing, combined with a desire to
share to developers everywhere what I can.
</p>
<p>
Syntax Error is a monthly newsletter that helps software developers turn a
stressful situation into a joyful exploration. Each month, I craft a
newsletter sharing tips, stories, tools and experiences about fixing issues in
your code.
</p>
<p>
The next issue comes out August 17th and will feature tips for how to debug
your CSS issues. If you'd like to get one for yourself, you can subscribe at
<a href="https://www.syntaxerror.tech/#/portal/signup">syntaxerror.tech</a>. I
also offer the issues in the web or via RSS feed if you prefer those over
emails.
</p>
Projects I'm proud of: Turku ❤️ Frontend
2023-08-08T00:00:00Z
https://hamatti.org/posts/projects-im-proud-of-turku-frontend/
<p><em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a> is a month-long event that takes place in August each year that focused on blogging and other serialized content. The goal is to stoke the fires of creativity and allow bloggers and other content creators to mingle in a shared community while pushing each other to post more regularly.</em></p><p><em>This week's theme is <strong>Introduce yourself</strong></em><strong> </strong><em>and I decided to do that through sharing a few projects that I've done since I think these stories will share quite a bit about who I am as a human being as well.</em></p><p>Turku ❤️ Frontend is the community and the project that I'm most proud of in the world. In my early-to-mid 20s, I struggled a bit because everything I was working on was a project created by someone else. I really wanted to find something in life that would be my creation and that would grow into something much bigger than that. Turku ❤️ Frontend became that.</p><p>Now, I've been running it for 7.5 years and the community is more active than ever.</p><h2 id="from-humble-beginnings">From humble beginnings</h2><p>In 2015, when I had done a career switch from software development to community management (see yesterday's post about Boost Turku), I wasn't sure if I'd stay in the new field. I wanted to make sure that if I'd end up returning to a dev job after my time at Boost, I'd still have some clue about development.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/turkufrontend-beginnings-tweet.png" class="kg-image" alt="A tweet by @hamatti: "Turku <3 Front end is a new meetup group for everyone who's into #frontend development in #turku. Let's get started"" /></figure><p>In December of 2015, I sent a tweet and a Facebook post suggesting a meetup group for developers and students interested in frontend development. In quite a short time, the initial Facebook group grew into dozens and then crossed 100 people, mostly from people I knew from university and startup scenes.</p><p>In a follow up tweet I tagged every tech company I could think of and some of them replied with an enthusiatic yes.</p><p><em>Sidenote: The meetup group wasn't the real first iteration though. I originally planned to start a small, maybe 4 people lunch & learn group with whom we'd gather for lunch once or twice a month to teach each other something. Before the first lunch, the group had already turned into a meetup group. The lunch concept ended up kinda happening in 2019 when I <a href="https://hamatti.org/posts/helsinki-dev-lunch/">started Helsinki Dev Lunch</a>.</em></p><p>Our first meetup was hosted at Poutapilvi, had two speakers from a fellow dev community HelsinkiJS and we had around 25 developers in the audience.</p><p>This was during the time in Turku where there were not really active developer communities. There used to be some but most had stopped or were nearly done.</p><h2 id="promoting-positive-developer-culture">Promoting positive developer culture</h2><p>During the non-summer months, we organize monthly meetups where we get a couple of speakers to do 30-minute presentations about various frontend related topics, hosting them in local tech companies' offices.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-14.png" class="kg-image" alt="A group of people sitting in rows of chairs, listening to a person speaking in front of the room" /><figcaption>One of our meetups at Reaktor's office, May 2022</figcaption></figure><p>We want to provide an opportunity for developers to see different companies and their cultures through visiting the offices and offer opportunities to have discussions with the speakers, the hosts and the fellow participants. In our latest meetup, we started with a pizza baking workshop where everyone got to make and bake their own pizza in pizza ovens before we kicked off the tech talks.</p><p>We want to encourage new people to try out public speaking and offer opportunities for speakers to share their knowledge, practice their craft and gain new audience.</p><p>Before, between and after the talks we have time for the community to mingle – which I think is the most important part of all this. We usually continue the night in some local pub for more discussions after the official program is over and the doors to the office closed.</p><p>We also do other stuff: we've organized a hackathon, <a href="https://medium.com/@turkufrontend/code-in-the-dark-turku-2dc96eca26a4">a Code in the Dark competition</a>, Turku Gives Back open source campaign and a brewery tour (with some tech talks mixed in). </p><p>Our community members have organized book clubs, open source projects, dev lunches and after work sessions too.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/07/image-15.png" class="kg-image" alt="A photo collage of Juhis in various group photos with Santa hats, chocolate boxes and Christmas cards" /></figure><p>For me, it's always been important to take care of the people who make it possible for me to organize all these things. Before the pandemic, I used to do a Christmas tour, visiting all the offices of that year's sponsors, bringing them chocolate and Christmas cards and taking selfies to say our thanks and celebrate the holidays.</p><h2 id="new-friends-new-careers-new-communities">New friends, new careers, new communities</h2><p>A lot of good has born out of the community. Many, me included, have made new friends by meeting them in our events, finding something common and going with it.</p><p>Many people have found a new job and a lot of prominent tech companies have opened offices in Turku since and we've been doing what we can to help them get involved with the community and find talent.</p><p>Together with my journey at Boost (see yesterday's post), this one was a career changer for me as well. By running the community with our team, I realized that I have a knack for building developer communities which turned into a Developer Advocate career and landed me a job at Futurice (more of that tomorrow!).</p><p>We've also inspired others to start organizing tech meetups. Namely, information security focused <a href="https://turkusec.fi/">TurkuSec</a> and more general themed dev meetup <a href="https://meetabit.com/communities/aurajoki-overflow">Aurajoki Overflow</a> (we had a lot of fun with Matias brainstorming for a name for that meetup).</p><p>At the time of writing this, we've had 50 meetups with around 500 different people participating. That's a positive impact being built there and we're nowhere near finished – there's way more to come!</p>
Projects I'm proud of: Boost Turku & Startup Journey
2023-08-07T00:00:00Z
https://hamatti.org/posts/projects-im-proud-of-boost-turku-and-startup-journey/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p>
<em>This week's theme is <strong>Introduce yourself</strong></em><strong> </strong><em>and I decided to do that through sharing a few projects that I've done
since I think these stories will share quite a bit about who I am as a human
being as well.</em>
</p>
<h2 id="2013-a-community-hang-around">2013: A community hang-around</h2>
<p>
It all started in February of 2013. I was studyin computer science in
university and one evening during our peer-mentoring session in the university
basement, an engineering teacher I had never met before came to say hi and
mentioned they were looking for students to apply to a Finnish-Chinese
research project.
</p>
<p>
I didn't think much about it but after attending my friends' impro show in a
local pub the same night, I decided to write an application anyway. Wasn't
likely to get in but hey, you never know. Maybe I'd regret if I didn't. Oh boy
how right I was.
</p>
<p>
I ended up getting into that project and one of the main side effects was that
through it, I was introduced to
<a href="https://boostturku.com/">Boost Turku</a>, the local student startup
community. 2013 I became a community member (or a "hang-around" as I'd call my
role), attending events, learning a lot about business and tech and design and
meeting a lot of great people.
</p>
<p>
I then spent 2014 in Silicon Valley working for a startup and after a bit when
I came back home, in fall of 2015 I got a job at the community as a community
manager. I spent two wonderful years in that role.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-12.png" class="kg-image" alt="A room full of people sitting, listening to a man on a stage speaking." />
<figcaption>
A cyber security keynote event with Benjamin Särkkä (on stage) and Mikko
Hyppönen
</figcaption>
</figure>
<h2 id="2015-2017-community-manager">2015-2017: Community Manager</h2>
<p>
While working in Silicon Valley, I had gotten a lot of inspiration about that
culture and wanted to bring what I had learned back to the community that had
helped me get there in the first place.
</p>
<p>
Boost Turku is a non-profit organization funded by local city and
universities. Throughout the years we organized all sorts of events to get
students excited about enterpreneurship and collaborated with universities'
business and tech studies to do guest lecturing and being in the jury of
different hackathons and business idea courses.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/DSC_0016.jpg" class="kg-image" alt="A group of people sitting in a half circle on chairs while a workshop host is sitting on a floor in the middle with random sortment of items next to them" />
<figcaption>One of our workshops with Esteve</figcaption>
</figure>
<p>
One of the cool programs we hosted was <strong>Dropout Academy</strong> which
in its first year was a peer-to-peer workshop series aimed to teach practical
skills that schools weren't teaching. We'd have various members of the
community teach other people what they knew: how to build a website, how to
negotiate during sales, how to run an agile team, how to do email and Facebook
marketing, etc. During the second year, we pivoted a bit and brought our
teachers from outside the community but otherwise kept the idea the same.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-13.png" class="kg-image" alt="9 framed diplomas with Boost Dropout Academy logo" />
<figcaption>
Boost Dropout Academy professor diplomas (graphic design is my passion)
</figcaption>
</figure>
<p>
I created diplomas for everyone who finished and we hosted a non-graduation
party where we celebrated what we had learned from each other.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-11.png" class="kg-image" alt="A group of people standing in a semicircle with two workshop hosts standing in the opening" />
<figcaption>Startup Journey workshop</figcaption>
</figure>
<p>
Every summer, we organized an award-winning early stage startup accelerator
program <strong>Startup Journey. </strong>During a 10-week program, a dozen or
so teams with startup ideas got to work on their ideas with daily mentoring
from the top experts in Finland on their own fields. We had coaches help the
teams with team building, service and UX design, finances, funding, legal,
product development, marketing, pitching and various other topics. Every
Wednesday one team was responsible for hosting a community BBQ event where the
entire community would come together to celebrate summer and meet each other.
</p>
<p>
I'm forever grateful to every coach who joined our accelerator to help the
next generation of entrepreneurs.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-10.png" class="kg-image" alt="A group photo of about 30 people on a stage" />
<figcaption>Startup Journey Demo Day 2017</figcaption>
</figure>
<p>
At the end of the summer, we hosted Demo Days where the teams pitched their
projects to a jury and then we all celebrated the work everyone had put in and
the friends we made along the way.
</p>
<p>
On both summers, we also organized programming courses for non-technical
students, helping people learn the basics of web development through workshops
and mentoring.
</p>
<p>
This was my first job as a community manager and it ended up becoming the new
career direction for me. Throughout these two years I realized I have good
skills in community building and great interest to learn from others and to
help others learn and find other people who share their passions.
</p>
<p>
Boost Turku is truly a community-driven community where people want to
participate and help each other out. During my time there, we brough together
so many students, experts, academics and entrepreneurs and helped many find
something new and exciting to their lives.
</p>
<p>
I also got to meet a lot of people in the other student entrepreneurship
societies in Finland, traveling far and wide across Finland to meet people, to
learn their best practices and sharing ours and building a great
interconnected national community. Also found my new best friend during those
trips.
</p>
<h2 id="2018-">2018-></h2>
<p>
After my community manager job ended, I ended up staying in the community as a
mentor, coach, speaker, workshop host and continued more closely with a few of
the startups from the two programs. It's great to be part of such a community
through which I've made so many great life-long friendships and have reached
new heights in my professional life.
</p>
<p>
The entire journey I've gone through with Boost and its amazing community has
shaped me into what I am and has had enormous effect on my life. The early
inspiring and encouraging response I got when I joined led me to work in
Silicon Valley, the community manager position rocketed me into a new career
where I found my opportunity to prosper and the lovely people I met became
very good friends for life.
</p>
Tools don't matter - until they do
2023-08-06T00:00:00Z
https://hamatti.org/posts/tools-dont-matter-until-they-do/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<h2 id="tools-don-t-matter-in-the-beginning">
Tools don't matter in the beginning
</h2>
<p>
My #1 tip for any aspiring blogger, who thinks about starting a blog is to
forget about the tools: your editor, how to build your site,
<a href="https://hamatti.org/posts/what-makes-a-blog-post-a-blog-post/">where to publish it</a>. Focus on your writing and getting that started. You can always move your
old posts to a new system - either automatically or if that's not available,
manually. Doing it manually is painful but believe me, it's better than
spending months wondering which tech to use and not writing a single post
because of that.
</p>
<p>
It's similar for starting your software development journey. If you ask for
recommendations for your first programming language, framework, code editor /
IDE or best cloud to deploy your apps, you'll get as many answers as you have
people to ask from. And it can get overwhelming.
</p>
<p>
But most important thing is to pick one and get started. The skills are
transferable, the tooling can be changed/updated/upgraded as you learn more
about what you need and what kind of tooling fits you. You need to put in the
work and hours though before you really know what you need what works.
Otherwise, you'll spend more time picking up new tools and figuring out which
one to use than you'll spend learning and creating things.
</p>
<p>
A bad or misfit tool can be a hinderance that takes the joy out of doing
things. At that point, it's worthy to look elsewhere and try out a different
tool. We humans come in many shapes and forms and there's no silver bullets so
like with everything in life, be open for change and experimenting when
there's real need. But not just when something new is shiny – especially at
the beginning.
</p>
<h2 id="until-they-do">Until they do</h2>
<p>
I'm writing this post at the end of a big website rewrite. I had managed to
publish on the same build (with small iterative improvements) for almost 5
years (proud of myself!). I had finally reached a moment where the desire to
rewrite didn't come from procrastination or "ooh, a shiny new thing" but from
months and years of built up "I wish my blog could do X" and having achieved X
through bad workarounds and hacks.
</p>
<p>
I had reached a point where my tooling truly started to matter for me and it
was time to redo things. I had reached a similar point in 2020 when I switched
from Markdown to Ghost as I had noticed my creativity drop heavily from the
bad tooling. Now I had outgrown my tools and felt that since I can very
comfortably maintain a regular weekly publishing pace, it was a good time to
do the rewrite.
</p>
<p>
One thing I had been wishing for years (and even tried by building a locally
running Django app) was a way to mark my different blog posts as related to
each other. Now I have a proper system in place for that and I've started to
retroactively add those connections to some older posts. It's very handy for
my blog series (like <a href="https://hamatti.org/rust">Learning Rust</a>) or
when writing about similar topics and referring to older blog posts.
</p>
<p>
I was also at the point where I had been writing for long enough that I knew
what I needed from my tooling to make writing enjoyable and get my creative
juices flowing. Ghost's editor was really good for that and the original
switch from Markdown to Ghost taught me the importance of an enjoyable editor.
</p>
<p>
Now I'm in the middle of switching to
<a href="https://notion.so/">Notion</a> (I already have some future posts
written with it but this one I'm still typing in Ghost as I want to get my
buffer emptied here first before doing the move). I have all the plumbing done
and experimented with.
</p>
<p>
Notion has similar, maybe even better writing and editing experience than
Ghost and on the technical side is different to handle. It requires a bit more
work to handle but once that work is put in up front, it allows me to do
behind the scenes magic that Ghost just doesn't.
</p>
<h2 id="once-you-re-there-spend-effort-learning-your-tools">
Once you're there, spend effort learning your tools
</h2>
<p>
While the choice of a tool isn't important in the beginning, as soon as you
become comfortable with the stuff that you're doing, I highly recommend
spending time and effort learning what your tools can use.
</p>
<p>
If it's your blog, read the user manual/documentation and read blog posts from
people using it. There's so much under the surface that we don't usually
notice that can make our lives so much better and our creation time more
efficient. Learn the keyboard shortcuts or quick commands. Learn how to use a
command palette if one exists. Learn how to customize it to your need and
learn what you can automate.
</p>
<p>
Whatever tool you use, make a habit out of reading their changelog. That's the
best place to see what new features they've added in the new update.
</p>
<p>
Share your tips forward! Write a blog post or post about them to social media.
Even the most mundane things may be a new thing to someone else.
</p>
<p>
In one of my previous workplaces, I would host a "show a cool trick with your
code editor" sessions every now and then, inviting my colleagues to share how
they use their tools. In every team I'm in, we talk a lot about the tools we
use and I always aim to build a culture where sharing small bits and bobs is a
daily habit.
</p>
<h2 id="share-your-one-tip-or-trick-with-a-tool-you-use">
Share your one tip or trick with a tool you use?
</h2>
<p>
Let's start a sharing discussion going on Mastodon! Reply to the toot below
via Mastodon and share a tip from one of the tools you use. No matter what
tool or what you do with it – there's likely someone out there who's using the
same thing and hasn't heard your tip before.
</p>
What makes a blog post a blog post?
2023-08-05T00:00:00Z
https://hamatti.org/posts/what-makes-a-blog-post-a-blog-post/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p>
Most of my blog posts from the past 4 years in this blog are rather similar in
form and function. Other than few exceptions, they are mostly around 1000
words each. They are written in conversational style and reflect on my
personal opinions, experiences and desire to share knowledge.
</p>
<h2 id="the-many-faces-of-blog-posts">The many faces of blog posts</h2>
<p>
I also read a ton of blogs. As of writing, I have 142 blogs that I follow in
my main RSS feed and I have a couple of other feed lists (like the recent
<a href="https://github.com/outcoldman/hackernews-personal-blogs">Hacker News list</a>
and
<a href="https://docs.google.com/spreadsheets/d/13zd0_xoCclOE5zn0PUsOphJTa2rkJoCwEj7gz1_OW5I/edit#gid=0">the Blaugust 2023 list</a>) that I follow on a secondary feed and pick ones I enjoy to my main list.
</p>
<p>
Many of those blogs are nothing like mine and I enjoy them a lot. There are
short daily blogs that are more like individual notes of distinct things.
There are more visual blogs. There are story-based blogs.
</p>
<p>
A blog post isn't really bound by many rules, if any. Or at least, the rules
are really up to the person reading what they decide to count as blog posts
and what kind of content they enjoy.
</p>
<p>
Participating in Blaugust and being exposed to a lot of different type of
bloggers and their blogs, I've started considering what my perception of a
blog post is.
</p>
<h2 id="my-inner-critic">My inner critic</h2>
<p>
Especially since I have this inner critic in my head that is pushing me to a
certain mould and format.
<em>"What will your readers think if you do X?", </em>it keeps nagging at my
ear. One blog post every year is my
<a href="https://hamatti.org/posts/merry-christmas-2022/">Merry Christmas post</a>. It completely breaks my format and is a reminder to myself that there are
more important things than hitting my Wednesday quota. Yet, the inner critic
keeps telling me I'm cheating when I publish those posts instead of
<em>real blog posts.</em>
</p>
<p>
I even built a separate category called
<a href="https://hamatti.org/snacks/">🍿 Snacks</a> for code snippets because
I felt they weren't blog post-y enough. And I have
<a href="https://hamatti.org/gaming/">a "secret" gaming blog</a> because at
some point I felt gaming content was way too different in style compared to my
main blog that I didn't want to mix them. I have since written and published
more gaming related content on this blog and will probably migrate those few
gaming posts into this main feed as well.
</p>
<p>
Even now, with "post every day" challenge of Blaugust, I still find myself
publishing the same type of content. Most of these are roughly 1000 words and
fit the requirements of my inner critic. Which is bit of shame because lately
I've been reading so many great blogs that are not bound by similar silly
restrictions and are very much enjoyable.
</p>
<p>
I have been working towards growing my audience and readership (somehow, I'm
nearing 50k monthly visitors) and consistency definitely helps there. Yet, I
keep thinking if I'm too worried about things like that and limiting my
creativity by not allowing myself post things. In my own blog. Where I'm the
only gatekeeper.
</p>
<h2 id="digital-gardens-instead-of-blogs">Digital gardens instead of blogs?</h2>
<p>
Many of the people I follow have moved from writing a blog with timestamped
chronological posts into building
<a href="https://maggieappleton.com/garden-history">a digital garden</a> that
takes more wiki-style approach of content pieces that get updated and written
a bit here and bit there and are ever-growing and updating.
</p>
<p>
A great example of this is <a href="https://ruk.si/notes">Ruksi's notes</a>.
It's the one that I always link to people who feel like writing long-form blog
posts feels too much but who would like to start documenting their learnings
and experiences into their website. (He also has
<a href="https://ruk.si/notes/marketing/blogging/">a few paragraphs about blogging</a>!)
</p>
<p>
I think this can be a great way to get started with writing and building a
body of work on your website that will be helpful to you and others in the
world. Whenever you run into something new you learned or something you helped
someone else do, write it down and share it. In the wide world, there's always
someone who's pondering the same things.
</p>
<h2 id="minimum-blog-feed-criteria">Minimum Blog Feed Criteria</h2>
<p>
I started really thinking about this topic as I ran into
<a href="https://loufranco.com/blog/minimum-blog-feed-criteria">Lou Franco's post Minimum Blog Feed Criteria</a>
around the same time I was also filtering the Hacker News feed list and having
run into a lot of content that I wasn't interested having in my blog feed.
</p>
<p>Lou has his criteria as:</p>
<p>Here’s my list of things your feed should have:</p>
<blockquote>
<strong>A title</strong>. For some reason, there are a bunch of untitled
feeds. The <code><link></code> tag has a <code>title</code> attribute,
and I’m pretty sure that if you leave it blank, most readers will pick up the
site’s <code><title></code> tag.<br /><br /><strong>The full post</strong>. I don’t know if it’s intentional or just a default of some blog software.
If you have a feed, I recommend putting the full post in it.<br /><br /><strong>A recentish post</strong>. I know it’s hard to keep a blog up-to-date, but if you are going to add
your site to a list, go put up a new post if your latest is very old. Even if
it’s just a short intro and a few links to your best posts.<br /><br /><strong>Not too many posts</strong>. I possibly post too much, but there were a few blogs with several posts
each day. They were short posts, but I still found that they dominated my
unread list too much, so I ended up deleting them.
</blockquote>
<p>
It's quite similar to mine. <strong>The title</strong> is less important for
me as I often rename it in my feed anyway to remember who's blog I'm reading
and making it easier to find a specific blog. I don't remember blog names but
I do remember people behind the blogs.
</p>
<p>
I also like having <strong>full blog posts</strong> in my feed. If the post is
interesting enough, I'll go read it on the blog but I'm more likely to not. On
my main reading device iPad I use <a href="https://lireapp.com/">lire</a> that
fetches the full post regardless so this has been a lesser issue for me
lately.
</p>
<p>
Having a <strong>recent post</strong> doesn't matter to me as that's why I
love RSS. You can post once a year and with RSS I'll get it. No need to
appease The Algorithm with constant blogging, the RSS is equally fair to all
blogs.
</p>
<p>
Finally, the question of <strong>how often to post</strong>. If there's a blog
that posts multiple times a day and the content is closer to what I'd expect
in social media, I'm likely to remove it from the feed to make sure it doesn't
overwhelm the feed. But if there's good daily content, I'm all in.
</p>
<p>
I heavily curate my feed. I'm very eager to add new blogs if I run into a blog
post that is interesting and I'm looking forward to seeing what they write in
the future but I'm also rather eager to delete feeds if I notice there's been
weeks or months where I haven't opened a single post from a feed.
</p>
Where to publish your blog?
2023-08-04T00:00:00Z
https://hamatti.org/posts/where-to-publish-your-blog/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p>
A decision every blogger needs to make in the beginning (and for many us, over
and over through the years) is where to publish your blog. Options are almost
limitless and what guides your decision are your technical skills, budget,
blog's topic/theme and personal opinions.
</p>
<h2 id="easy-option-for-developer-blogs-dev-to">
Easy option for developer blogs: Dev.to
</h2>
<p>
If you're planning to write software development content and want an easy and
fast option, <a href="https://dev.to/">Dev.to</a> has a nice platform and
community. I used to write there for a while before migrating completely my
own blog here <a href="https://hamatti.org/blog">on my site</a>.
</p>
<p>
They offer a free blog platform where you write your blog posts in
<a href="https://daringfireball.net/projects/markdown/syntax">Markdown</a>
format (it's a very popular format that is mostly regular text with some
special codes for formatting and links etc).
</p>
<p>
A nice benefit is that if your blog posts become popular, there's a large and
engaging audience on the platform. A minor downside is that all the blogs look
the same and are in the dev.to domain.
</p>
<p>
Since it takes a minute or two to create an account and get your ready to
start writing, I recommend it as the first step.
</p>
<h2 id="easy-option-for-everyone-hosted-cms-ghost-wordpress-or-squarespace">
Easy option for everyone: Hosted CMS (Ghost, WordPress) or Squarespace
</h2>
<p>
If you want a bit more control over the site (design, other pages than just
blog, making it <em>your site</em>), you can look into hosted or self-hosted
content management systems (CMS) like
<a href="https://ghost.org/">Ghost</a> or
<a href="https://wordpress.com/">WordPress</a>.
</p>
<p>
These systems give you an opportunity to build your own website, using
pre-designed themes and run your blog without writing any code with a nice
editor experience. Both Ghost and WordPress can be used as hosted options (you
sign up on their site and run the blog there) or self-hosted (you download the
software, install it on your own server and take care of everything) and since
they are open source solutions, you can do whatever you want to expand them if
you have the skills (or there's someone who already built a plugin or you can
pay someone to customize stuff).
</p>
<p>
I've had at least half a dozen blogs on these platforms. Right now, I use
paid, hosted Ghost solution to run
<a href="https://www.syntaxerror.tech/">Syntax Error newsletter</a> and I have
a headless Ghost (more of this below) for this very blog. I love their mission
as a non-profit open source solution and the editor experience is top notch.
</p>
<p>
Some of the hosted CMS services offer free tiers and some require some money
to get started.
</p>
<p>
There's also an option at
<a href="https://www.squarespace.com/">Squarespace</a> which might be
<em>the easiest</em> option to get started and they have gorgeus templates out
of the box. It costs a bit monthly but if you spend more than 5 minutes on
Youtube, you can probably find a content creator sponsored by them and can
grab some discounts or free trials.
</p>
<h2 id="fediverse-writefreely-or-micro-blog">
Fediverse: WriteFreely or Micro.blog
</h2>
<p>
Mastodon has brought
<a href="https://en.wikipedia.org/wiki/Fediverse">Fediverse</a> to everyone's
attention recently and there's also Fediverse blogging platforms like
<a href="https://writefreely.org/">WriteFreely</a> and
<a href="https://micro.blog/">Micro.blog</a> where you can blog while being
connected to the whole ecosystem.
</p>
<p>
As these blogs are connected within the Fediverse, it's easy for others to
start following you through their accounts in other Fediverse services like
Mastodon.
</p>
<p>
You can either join an existing instance or host your own if you feel
adventurous.
</p>
<h2 id="static-site-generators-your-own-website">
Static site generators & your own website
</h2>
<p>
If you're a developer, a great option is to build your website and blog from
scratch. You get exactly what you want (or can build) and gain full control
over every bit of it. With hosting solutions like
<a href="https://www.netlify.com/">Netlify</a> or
<a href="https://vercel.com/">Vercel</a>, deploying a website is as easy as
dragging the folder into their website or hooking it up to your GitHub
repository.
</p>
<p>
There are a lot of options for choosing the static site generator. Popular
options are <a href="https://www.11ty.dev/">Eleventy</a> (my absolute
favorite), <a href="https://gohugo.io/">Hugo</a>,
<a href="https://jekyllrb.com/">Jekyll</a>,
<a href="https://www.gatsbyjs.com/">Gatsby</a>,
<a href="https://getpelican.com/">Pelican</a>,
<a href="https://astro.build/">Astro</a> and
<a href="https://jamstack.org/generators/">plenty more</a>. Each of them have
their own characteristics and specialities but you can start by choosing one
and seeing where it leads you to.
</p>
<p>
I have written a
<a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/">3-part tutorial on how to set up a site with Eleventy, Netlify CMS and
GitHub</a>.
</p>
<h3 id="writing-in-markdown">Writing in Markdown</h3>
<p>
One option with static site generators is to write your blog posts in Markdown
and the static site generator creates the HTML blog with blog index and
individual pages based on those Markdown files. What it means in practice is
that all you need is a code editor and you're good to go. Each site generator
also has a ton of community-created starter kits and themes so you don't need
to start from scratch.
</p>
<p>
Markdown is a great format also because it allows you to change underlying
static site generator to another without having to do a ton of manual
migration.
</p>
<p>This blog originally started with Markdown files.</p>
<h3 id="writing-with-headless-cms">Writing with headless CMS</h3>
<p>
While I started with Markdown, I noticed at some point that my creativity and
desire to write had dropped a lot. I blamed the tooling: writing prose in code
editor just didn't feel inspiring and great. Then I found Ghost and have been
using it as a <em>headless CMS</em> ever since.
</p>
<p>
What's a headless CMS then? It means I write my content (these blog posts) on
a CMS and then fetch those posts into my project. Most people have set it up
to happen at build time, I have a bit more overly complex setup.
</p>
<p>
This lets me combine the flexibility and customizability of static sites with
the writing and editing experience of a CMS.
</p>
<h2 id="don-t-overthink-it">Don't overthink it</h2>
<p>
In practice, if need be, you can migrate your blog posts from one platform to
another so the most important part is to start. This blog currently has blog
posts that have been written in WordPress, in Medium, with Markdown and with
Ghost CMS and they all now live happily in one place.
</p>
<p>
The hardest blog post to write is the second one. First one often comes with
the excitement of starting a blog, maybe telling the audience that 1) you've
started a blog and what you plan to write about and 2) what tech stack you're
using. And then the second post never happens.
</p>
<p>
So focus on writing, experiment with platforms and choose one that feels
comfortable to write in. Everything else can be figured out later. Later this
week, I'm writing a blog post about how tooling matters until it doesn't but
to get started, don't overthink it.
</p>
<p>
A great thing with all the options above is that setting up an RSS feed is not
rocket science. RSS feeds can be incredibly effective in helping your audience
find you when the second and third ... and the fiftieth blog post is
published.
<a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed/">I think every blog should have one</a>.
</p>
<p>
I have previously written about
<a href="https://hamatti.org/posts/you-should-start-a-blog-today/">the reasons why you should write a blog</a>
and
<a href="https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/">how for me, it's a great refactoring tool for software projects</a>.
</p>
Build an idea bank and never run out of blog ideas
2023-08-03T00:00:00Z
https://hamatti.org/posts/build-an-idea-bank-and-never-run-out-of-blog-ideas/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p><strong>"What should I write about?"</strong></p>
<p>
I think a lot of us blogger or those who decided to never start a blog have
asked themselves and maybe others around them that question. Whether it's
about insecurities of thinking nobody would find their thoughts worth reading
or about worries of maintaining a good, stable schedule of regular publishing,
it's a question worth asking.
</p>
<p>
I think my first ever blog post was written around 2002 or 2003 as the
teenager me built my first website (unfortunately none of the early sites and
blogs have survived). I remember writing two blog posts: one about kindness in
daily interactions and another about how much harder it is for positive news
to travel compared to negative ones.
</p>
<p>
More seriously I started writing around 2013 when I was studying and got my
first developer job. Over the years my blogging was very much on/off and I
started almost as many new blog sites than I had posts.
</p>
<p>
Through 2018 and 2019 I seriously picked up the proverbial pen and started to
write. By October of 2021, I had
<a href="https://hamatti.org/posts/over-a-year-of-weekly-blogging/">succesfully written and published blog posts on a weekly basis for a
year</a>.
</p>
<p>
One thing I've learned throughout these years about ideas is that the more you
write, the more you come up with topics and ideas to write about. It's a
muscle worth keeping warm. By being a blogger, I find myself noticing things
around me and projects or features or other things that I've done as worthy of
a blog post.
</p>
<p>
But if you really want to keep up a good pace, sometimes you risk running out
of ideas, especially if life becomes busy. Maybe you're in a situation in life
and work that you're not doing much that you could write about.
</p>
<h2 id="idea-bank">Idea bank</h2>
<p>
My solution to this has been keeping an <strong>idea bank </strong>in my
note-taking app. Every time I come up with an idea that's not interesting or
big enough yet (or too big and needs more research time), I write it down to
my idea bank.
</p>
<p>
Occasionally, I do brainstorming days when I write down as many ideas and blog
post titles as I can. All this to help future me on a quiet and non-inspiring,
non-creative week to pick something up and start writing.
</p>
<p>
Every idea or topic is one item on a list. Sometimes that's all it needs to be
ready to be picked up and written into a post. But often it grows in the bank
before it's ripe for picking. I add bullet points as new related thoughts
wander through my mind (there's a lot happening in my mind, believe me). I add
links to articles that I read from other people that are relevant.
</p>
<p>
For bigger posts, I do active research and collect more ideas and start
fleshing out the outline and what I want to say. This is especially good for
things that require a lot of examples or references.
</p>
<p>
At the time of writing this, I have somewhere around 100 to 120 different blog
post ideas and drafts in the idea bank waiting for the right day.
</p>
<h2 id="do-you-have-something-similar">Do you have something similar?</h2>
<p>
How are you solving this issue (or is it even an issue to you)? What kind of
tools and processes do you use to keep your notes in good order?
</p>
<p>
Let me know and participate in the discussion through Mastodon via the post
below and share your tips to the blogosphere!
</p>
A bunch more of small game reviews: Dredge, Dishonored, Farm Keeper
2023-08-02T00:00:00Z
https://hamatti.org/posts/a-bunch-more-of-small-game-reviews-dredge-dishonored-farm-keeper/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p>
Over the past few weeks, I've been playing new games and revisiting old
favorites. Just like
<a href="https://hamatti.org/posts/bunch-of-small-game-reviews/">last time</a>, here's three small reviews of them.
</p>
<h2 id="dredge">Dredge</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image.png" class="kg-image" />
<figcaption>
Dredge - <a href="https://www.dredge.game/">https://www.dredge.game/</a>
</figcaption>
</figure>
<p>
I was actually looking to buy
<a href="https://store.steampowered.com/app/1868140/DAVE_THE_DIVER/">Dave the Diver</a>
and while I was looking through a few reviews about it on Youtube, the
platform recommended me a video
<a href="https://www.youtube.com/watch?v=GnNkRGAjo8Q">I Played 100 Days of Dredge by floydson</a>. I figured I'll probably not buy the game so I started watching without
being worried about spoilers. I made it maybe few minutes in before deciding
to stop watching and actually buy the game and play it through unspoiled
myself.
</p>
<p>
It was a great decision.
<a href="https://store.steampowered.com/app/1562430/DREDGE/">The game</a> was
on Steam Summer Sale for 19,99e and with Verified Steam Deck compatibility, I
picked it up. The game is a mixture of playing as a fisher in a beautiful
archipelago (this had some strong Stardew Valley appeal to me) and
Lovecraftian horror game (I'm not a fan of horror in any format, it crawls
under my skin too easily).
</p>
<p>
The art style is beautiful, the soundscape is brilliant and the characters and
the story have real appeal. It has a similar super engaging daily loop that
Stardew Valley has despite being a rather different game. But after every
in-game day when you're docking to the safety of a dock, it's so hard to put
the game away as you're always "almost there" to get to a next milestone like
new upgrade to your ship or reaching a new part of the story.
</p>
<p>
Game breaks down to three main gameplay loops: fishing minigames where you
travel around the archipelago looking for fishing spots and play small
minigames to catch the fish; gathering materials, selling fish and ugprading
your boat so you are better equiped to face the challenges of the world; and
third, progressing in the story that introduces a lovely cast of different
characters, all with their own distinct personalities and quirks.
</p>
<p>
I can highly recommend picking up Dredge, especially if you have Steam Deck.
It's perfect for those short gaming sessions on the go but also works really
nicely when docked on a larger screen at home.
</p>
<h2 id="dishonored-1-2">Dishonored 1 & 2</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-1.png" class="kg-image" />
<figcaption>Dishonored - https://www.igdb.com/games/dishonored/</figcaption>
</figure>
<p>
I originally played both Dishonored titles when they came out and enjoyed them
both a lot. Over the years, while watching other people play or speedrun the
games, I've grown to realize how little creativity I have when it comes to
games (Zelda Tears of Kingdom had similar effect) and finding alternative
solutions to problems.
</p>
<p>
With the original one being on a big sale
<a href="https://store.steampowered.com/app/205100/Dishonored/">in Steam</a>
for 2,49e during the Summer Sale, I just had to pick it up for Steam Deck (I
own the games for PS4 originally). I'm still daily amazed by how well Steam
Deck works with such a variety of different games.
</p>
<p>
Dishonored games are first-person action games that put you into the shoes of
an assasin called Corvo (and the daughter of the empress, Emily in the sequel)
who has magical abilities (like dashing, force push or commanding rats to do
the killing). The game offers a few different ways to play: you can either go
around killing the enemies or take a more stealthy route avoiding being seen
and dealing with enemies in a non-lethal way.
</p>
<p>
The gameplay is very spot on and the controls are fluent and the story still
holds on after a decade. Even after knowing the story and remembering some
bits of the game play and maps, I still managed to find new things, new routes
and new ways to solve problems on my most recent playthrough.
</p>
<h2 id="farm-keeper">Farm Keeper</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-2.png" class="kg-image" />
<figcaption>
Farm Keeper - https://store.steampowered.com/app/2458940/Farm_Keeper/
</figcaption>
</figure>
<p>
Something completely different is
<a href="https://store.steampowered.com/app/2458940/Farm_Keeper/">Farm Keeper</a>, which is a tile-laying farming game that asks you to put your puzzler hat
on to figure out how to keep paying rent while growing your farm.
</p>
<p>
You start with a small farm, planting a few crops and each day, get an
opportunity to grow with new tiles that introduce new crops, animal husbandry,
fishing, fruit trees and all sorts of other options to grow your field.
</p>
<p>
It's not a chill "let me grow my parsnips while I ponder life" type of game as
the rent goes up fast and you're constantly at the edge of trying to figure
out how to make the ends meet. (If you're looking for a chill game like that,
I'd recommend taking a look at two favorites of mine,
<a href="https://www.stardewvalley.net/">Stardew Valley</a> and
<a href="https://store.steampowered.com/app/1455840/Dorfromantik/">Dorfromantik</a>)
</p>
<p>
A single session doesn't take too long so once again this makes a great
addition to my Steam Deck collection.
</p>
<h2 id="what-have-you-been-playing-lately">
What have you been playing lately?
</h2>
<p>
Comment below via Mastodon to let me know what you've been playing! I'd love
to find new games, especially some hidden indie gems that are available for
PS4, Switch or via Steam.
</p>
Blaugust 2023, here we come
2023-08-01T00:00:00Z
https://hamatti.org/posts/blaugust-2023-here-we-come/
<p>
<em><a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>
is a month-long event that takes place in August each year that focused on
blogging and other serialized content. The goal is to stoke the fires of
creativity and allow bloggers and other content creators to mingle in a
shared community while pushing each other to post more regularly.</em>
</p>
<p>
Last week I was browsing through the fediverse when I came across a concept
and community called
<a href="https://aggronaut.com/2023/07/12/blaugust-2023-is-coming/">Blaugust</a>, a combination of blogging and August and I couldn't keep myself away from
such fun idea. In the past I've participated in such amazing projects as
<a href="https://nanowrimo.org/">NaNoWriMo</a>,
<a href="https://github.com/julython/julython.org">Julython</a>,
<a href="https://adventofcode.com/2022/about">Advent of Code</a> and
<a href="https://bringback.blog/">Bring Back Blogging</a>.
</p>
<p>
Blaugust probably also brings a lot of new readers to this blog. If you're
one, hello! I'm Juhis, a community builder, software developer, tech educator
and public speaker who
<a href="https://hamatti.org/posts/why-i-do-what-i-do/">wants to help others make the world a better place through technology.</a>
</p>
<p>
In this blog, I mostly talk about topics related to community and software
development, sprinkled with a bit of personal stories, opinion pieces and
video and table top gaming. There are over 200 blog posts in this blog so feel
free to take a look at what I've written before! And if you're a software
developer or dream of becoming one,
<a href="https://www.syntaxerror.tech/">sign up for my Syntax Error debugging newsletter</a>.
</p>
<p>My personal goals for Blaugust for this first year are:</p>
<ul>
<li>Write more</li>
<li>Experiment with different type of content</li>
<li>Learn from others and get inspired by the community</li>
<li>Help someone get started or restarted with blogging</li>
</ul>
<p>
If the sudden influx of new blog posts bothers you, I'll move back to a weekly
Wednesday pace after August so don't delete me from your RSS feed just yet
❤️️!
</p>
<h2 id="theme-weeks">Theme weeks</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-9.png" class="kg-image" alt="A colorful August calendar with each week having a different color and themes, starting from week 1: Welcome to Blaugust week, Introduce yourself week, Creator appreciation week, Staying motivated week and Lessons learned week" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<p>
I like themes and prompts and while I have my regular content coming out
through August, I'm definitely looking into the themes more.
</p>
<p>My rough initial plan is this:</p>
<ul>
<li>
First week, "Welcome to Blaugust" I'd like to write blog posts about
blogging itself, like preparation content for anyone who wants to start
blogging or become more productive at it.
</li>
<li>
Second week, "Introduce yourself" I'm planning to share each day a different
project that I'm proud of and through those projects and experiences provide
an insight into who I am.
</li>
<li>
Third week, "Creator appreciation" I'll focus on other creators. Each day,
I'm thinking of having a theme or category of content I enjoy reading,
watching or listening and then writing recommendations for people to find
new great content.
</li>
<li>
Fourth week, "Staying motivated" I have no clue yet, luckily it's four weeks
away.
</li>
<li>
Fifth week, "Lessons learned" I'll blog about this experience and how
Blaugust turned out but also of other things I've learned over the years.
</li>
</ul>
<h2 id="achievements-unlocked-">Achievements unlocked!</h2>
<p>
A fun extra thing that the organizers have for us is
<a href="https://aggronaut.com/blaugchievement-list/">Blaugchievements</a>
that are achievements you can reach by participating. With this first blog
post + kickoff of Blaugust, I have reached the following:
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-3.png" class="kg-image" alt="Blaugchievement gained: Reading the manual" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Reading the Manual </strong>– By the time you have arrived at this
page, you have probably already completed this blaugchievement. Essentially
this is here to instill the importance of actually reading the introduction
to Blaugust post.
</blockquote>
<p>I sure did read the announcement post!</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-4.png" class="kg-image" alt="Blaugchievement gained: Joining the cause" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Joining the Cause</strong> – This one is gained by
<a href="https://forms.gle/c3J3Cm6viZvXUCtj8" rel="noreferrer noopener">filling out the sign-up form</a>
and choosing to participate in this year’s Blaugust event. If for some reason
you cannot fill out the form, please contact me and I will collect your
information to register you directly.
</blockquote>
<p>Yup, I'm in!</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-5.png" class="kg-image" alt="Blaugchievement gained: Spreading the madness" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Spreading the Madness</strong> – This one is gained by promoting the
Blaugust event either on your own blog or through any of your favorite social
media channels. Again we are trying to get more bloggers active to revive
this art form.
</blockquote>
<p>
This blog post + a few <em>"getting excited" </em>Mastodon posts got me this
achievement.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-6.png" class="kg-image" alt="Blaugchievement gained: Friend of Wumpus" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Friend of Wumpus</strong> – This one is gained by signing up for the
<a href="http://discord.gg/KAXgK2E" rel="noreferrer noopener">official Blaugust Discord</a>. While this Discord was created for this event, it stays surprisingly
active all year round and becomes a locus of our community.
</blockquote>
<p>
This is the part I'm really looking forward as I really want to learn from and
get inspired by other writers and creators this month.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-7.png" class="kg-image" alt="Blaugchievement gained: Federation of bloggers" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Federation of Bloggers</strong> – Create a social media account on
any of the Fediverse platforms (Mastodon, Pleroma, Calckey, Misskey, etc) and
follow the official
<a href="https://gamepad.club/@Blaugust" rel="noreferrer noopener">Blaugust Mastodon</a>
account. This account lives on
<a href="https://gamepad.club/" rel="noreferrer noopener">Gamepad.Club</a>
which coincidentally is a lovely Mastodon Server if you don’t have one
already in mind. During the event, the Blaugust account will be boosting
posts made with the <strong>#Blaugust2023</strong> hashtag.
</blockquote>
<p>
I'm <a href="https://mastodon.world/@hamatti">@hamatti@hamatti.org</a> in
Mastodon, feel free to follow me there for all sorts of things about blogging,
technology, community building, events, life and philosophical ponderings.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-8.png" class="kg-image" alt="Blaugchievement gained: Welcome wagon" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Welcome Wagon</strong> – This one is gained by writing a blog post
based on the first week’s theme of welcoming folks to Blaugust. Hint, this
may also count as the “Spreading the Madness” achievement. The idea is to
get some activity in those early days of Blaugust to potentially expand our
members and have folks join mid-event.
</blockquote>
<p>Hint: you're reading it!</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/07/image-22.png" class="kg-image" alt="Blaugchievement gained: Recruit a friend" />
<figcaption>
by
<a href="https://aggronaut.com/blaugust-media-kit/">https://aggronaut.com/blaugust-media-kit/</a>
</figcaption>
</figure>
<blockquote>
<strong>Recruit a Friend</strong> – Convince another blogger to participate
in this year’s Blaugust event. This could be a brand new blogger but also
could just be convincing another blogger out there to participate. Again
much of the focus on Blaugust is to stir up activity in the community and get
bloggers active again.
</blockquote>
<p>
A couple of my friends have mentioned they might join Blaugust based on my
posts but so far, the only one I know for sure who has filled in the form is
Lucia with a great software development blog at
<a href="https://www.oh-no.ooo/">https://www.oh-no.ooo/</a>. You should
definitely head over there and add the blog to your RSS feed!
</p>
<p>
I've been luring Lehtu to start blogging for a while and I'm super proud he
got his <a href="https://lehtu.github.io/">blog</a> started for Blaugust!
</p>
<p>
That's a nice collection of achievements already! Let's see if I can manage to
get all the way through to Platinum Trophy by achieving all the others.
</p>
Blog comments via Mastodon
2023-07-26T00:00:00Z
https://hamatti.org/posts/blog-comments-via-mastodon/
<p>
For quite a while, I've wanted to enable some way to comment on my blog posts
as that can provide a great way to get more opinions, points of views and
corrections to the content I've written. I didn't want people to need to make
a new account to a new service just to write a comment.
</p>
<p>
Over the time, I've run into many great stories of people building something
similar. In December,
<a href="https://www.yusuf.fyi/posts/receiving-blog-replies-from-anywhere">Yusuf Bouzekri shared how he added webmentions to his blog</a>. In January,
<a href="https://andy-bell.co.uk/improving-likes-on-my-site/">Andy Bell shared improvements he made for the "likes" section in his
blog</a>. Then there was
<a href="https://jan.wildeboer.net/2023/02/Jekyll-Mastodon-Comments/">Jan Wildeboer and his Mastodon-based approach</a>
and finally this week in July,
<a href="https://cassidyjames.com/blog/fediverse-blog-comments-mastodon/">Cassidy James' story of him implementing comments based on Jan's work</a>
finally gave me the push needed.
</p>
<p>
Since writing this originally, I've also found that
<a href="https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/">Carl Schwan</a>
and
<a href="https://berglyd.net/blog/2023/03/mastodon-comments/">Veronica Olsen</a>
have implemented Mastodon comments to their blogs in the past as well.
</p>
<h2 id="my-approach">My approach</h2>
<p>
I decided to start with a basic approach and then I'll iterate upon it over
time as I see what I like and what not.
</p>
<p>
I use Eleventy and Ghost CMS to run my blog. When I want to enable comments on
my blog post, I first write it and publish it, then toot about it in the
fediverse. Once that is done, I can capture the Mastodon post ID and add that
to my post's
<a href="https://www.11ty.dev/docs/data-frontmatter/">front matter</a>:
</p>
<pre><code class="language-yaml">---
# other front matter first, then
mastodon_id: "110740556042408938"
---</code></pre>
<p>On build time, I inject this ID into the HTML for in the blog post page:</p>
<pre><code class="language-html">
<section id="comment-section">
<h2>Comments</h2>
<div class="comment-ingress">
Comment by replying to <a href="https://mastodon.world/@hamatti/110779197470760541">this post in Mastodon</a>.
</div>
<div id="comments" data-id="110779197470760541"">
<p>Loading comments...</p>
</div>
<div class="continue-discussion">
<p>Continue discussion in <a href="https://mastodon.world/@hamatti/110779197470760541">Mastodon</a> &#187;</p>
</section>
</code></pre>
<p>
For this first version, I ended up hard coding my account information and just
storing the ID of the individual post. This is different from Jan's approach
in which he always put the host, the account and the ID into the post's front
matter. I might implement that at some point to get a bit more flexibility.
</p>
<p>
I also added a <code><template></code> tag for an individual comment:
</p>
<pre><code class="language-html"><template id="comment-template">
<article class="blog-comment">
<div>
<img />
</div>
<div class="comment-content">
<div>
<div class="author"></div>
<div class="publish-date"></div>
</div>
<div class="comment"></div>
</div>
</article>
</template></code></pre>
<p>
On the runtime, I then check if there is a Mastodon ID in the
<code>data-id</code> attribute and if there is, I do two calls to the Mastodon
API: one to <code>https://[DOMAIN]/api/v1/statuses/[MASTODON_ID]</code> to get
the original post and another to
<code>https://[DOMAIN]/api/v1/statuses/[MASTODON_ID]/context</code> to get the
replies.
</p>
<p>
For each comment from these, I then copy the template, fill it in and render
that to the page.
</p>
<h2 id="code-snippets">Code Snippets</h2>
<p>
I was asked if I could provide more complete code snippets for this so here
they are. You may have to adjust some of these based on your stack.
</p>
<h3>Blog post front matter</h3>
<p>
I have this line in my
<a href="https://www.11ty.dev/docs/data-frontmatter/">front matter</a>:
</p>
<pre class="language-yaml"><code class="language-yaml">---
mastodon_id: "[your post id]"
---</code></pre>
<p>
The <code>[your post id]</code> comes from the Mastodon post URL. For example,
the id for this introduction post:
<code>https://mastodon.world/@hamatti/110808657202754329</code> is
<code>110808657202754329</code>.
</p>
<h3>Post template</h3>
<p>
On build-time, I use this template to inject the Mastodon post id into the
HTML element:
</p>
<pre class="language-html"><code class="language-html">{% if mastodon_id %}
<section id="comment-section">
<h2>Comments</h2>
<div class="comment-ingress">
Comment by replying to <a href="https://mastodon.world/@hamatti/{{ mastodon_id }}">this post in Mastodon</a>.
</div>
<div id="comments" data-id="{{mastodon_id}}">
<p>Loading comments...</p>
</div>
<div class="continue-discussion">
<p>Continue discussion in <a href="https://mastodon.world/@hamatti/{{ mastodon_id }}">Mastodon</a> »</p>
</div>
</section>
{% endif %}</code></pre>
<p>
The entire block is wrapped in <code>if</code> clause so it's only rendered if
I've added the Mastodon id to front matter. On line 7, I add it as a data
attribute so it's accessible via Javascript.
</p>
<h3>Client-side Javascript</h3>
<p>
Final part of the puzzle is fetching the replies from Mastodon API and showing
them after the post. On lines 51 and 57, you need to switch the base URL to
match your instance.
</p>
<pre class="language-javascript"><code class="language-javascript">function renderComment(comment, target, parentId) {
const node = document
.querySelector("template#comment-template")
.content.cloneNode(true);
const author = node.querySelector(".author");
let mastodonAcct = comment.account.acct;
if (mastodonAcct === "hamatti") {
mastodonAcct = "hamatti@mastodon.world";
}
let in_reply_to_id = comment.in_reply_to_id;
author.innerHTML = `<a href="https://hamatti.org/posts/blog-comments-via-mastodon/$%7Bcomment.account.url%7D" target="_blank">${comment.account.display_name} (${mastodonAcct})</a>`;
const commentContainer = node.querySelector(".blog-comment");
if (in_reply_to_id !== parentId) {
commentContainer.classList.add("indent");
}
const publishDate = node.querySelector(".publish-date");
const dateObj = new Date(comment.created_at);
const dateTime = `${dateObj.getDate()}.${
dateObj.getMonth() + 1
}.${dateObj.getFullYear()} ${dateObj.getHours()}:${dateObj.getMinutes()}`;
publishDate.innerHTML = `<a href="https://hamatti.org/posts/blog-comments-via-mastodon/$%7Bcomment.url%7D" target="_blank">${dateTime}</a>`;
const userComment = node.querySelector(".comment");
userComment.innerHTML = comment.content;
const avatar = node.querySelector("img");
avatar.src = comment.account.avatar_static;
target.appendChild(node);
}
async function renderComments() {
const commentsNode = document.querySelector("#comments");
const mastodonPostId = commentsNode.dataset?.id;
if (!mastodonPostId) {
return;
}
commentsNode.innerHTML = "";
const originalPost = await fetch(
`https://mastodon.world/api/v1/statuses/${mastodonPostId}`
);
const originalData = await originalPost.json();
renderComment(originalData, commentsNode, null);
const response = await fetch(
`https://mastodon.world/api/v1/statuses/${mastodonPostId}/context`
);
const data = await response.json();
const comments = data.descendants;
comments.forEach((comment) => {
renderComment(comment, commentsNode, mastodonPostId);
});
}
renderComments();
</code></pre>
<p>
with the <code><template></code> that is used as a base (this is in my
post layout):
</p>
<pre class="language-html"><code class="language-html"><template id="comment-template">
<article class="blog-comment">
<div class="comment-meta">
<div>
<img/>
</div>
<div class="comment-meta-text">
<div class="author"></div>
<div class="publish-date"></div>
</div>
</div>
<div class="comment-content">
<div class="comment"></div>
</div>
</article>
</template></code></pre>
<h2 id="considerations">Considerations</h2>
<p>
I'm pretty happy by how this turned out and how I was able to build most of it
in a short time while sipping a drink in a local pub. I love these kind of
small projects to get started with something and then improving upon it over
time.
</p>
<p>
Mastodon approach still requires people to have an account to comment but I
hope it's gonna be a good tradeoff since a lot of my audience already is in
Fediverse. And maybe this will encourage others to join.
</p>
What tech docs can learn from video game tutorials?
2023-07-19T00:00:00Z
https://hamatti.org/posts/what-tech-docs-can-learn-from-video-game-tutorials_/
<p>
Your job is to write the documentation, including some get started guides and
tutorials, for your company's or project's software. The software is beautiful
and it can do quite a few things – and as is often common for software or
libraries can be configured in many ways.
</p>
<p>
You jump on the documentation, going through from the start towards more
complex features and explaining and referring to the reference documentation
as you go on, providing the user all the knowledge they need to make their own
decisions.
</p>
<p>But is that the best approach?</p>
<p>
At the end of last year, I was brainstorming and researching ideas for how we
could improve our tutorials and guides for Firefox's browser extension
documentation website.
<a href="https://hamatti.org/posts/im-leaving-mozilla/">My journey there ended</a>
before I managed to craft those ideas into a reality but one path I ended up
following was to look into what video games are doing with their tutorials.
</p>
<h2 id="only-show-what-s-necessary-and-build-up-from-there">
Only show what's necessary and build up from there
</h2>
<p>
In
<a href="https://www.youtube.com/watch?v=-GV814cWiAw">Mark Brown's wonderful video essay GMTK: Can we improve tutorials for
complex games</a>, Mark mentions Bruce Shelley's idea of inverted pyramid of decision making
that
<a href="https://lifeandtimes.games/episodes/files/soundbite-bruce-shelley">Bruce talks a bit about in Soundbit podcast episode from January 2020</a>:
</p>
<blockquote>
<strong>Bruce Shelley:</strong> I had a phrase, I guess, when I worked at
Microprose Sid said it was my phrase. It's called the inverted pyramid of
decision making. So the idea is that you start the game with one unit and you
make a decision with it and then that opens up a door.Now you can make
two decisions. Or you build a second unit. Now you have two units to use. And
then you have four and you have eight and you have 16. And these decisions
like ramp up, so it starts off as a tiny bunch of decisions and it quickly
grows, balloons. And I think that does a great job of getting people engaged.
And we had this idea that you had 15 minutes to engage somebody or you'd lose
them.
</blockquote>
<p>
There are a couple of thoughts in that which really resonate with me. One is
that we don't have to start with all the complexity of our software or
library. Sure, let's provide the developer a link to resources if they want to
dive directly to the deep end right away but for those who are exploring,
let's start with small and add a few things at a time.
</p>
<p>
Another thought is the last sentence in the previous quote: "And we had this
idea that you had 15 minutes to engage somebody or you'd lose them." This is
often the case in technology as well. If someone's taking a look at your
project, you need to catch their attention, start small and guide them towards
more advanced topics bit by bit, providing a small pieces of feeling of
success at each point.
</p>
<p>
It can be very discouraging to follow a tutorial or read documentation for a
few hours only to realize that what you attempted to build didn't work for
some reason or if it did, maybe didn't solve the actual problem you had.
</p>
<p>
Building your tutorials in a way that take a small step at the time, provide a
success point after each step. This is not only great for dopamine hits and
engagement but also for making sure that the feedback loop remains tight.
</p>
<h2 id="goal-driven-rather-than-feature-driven">
Goal-driven rather than feature-driven
</h2>
<p>
Often in video games, the tutorials are shown to the player one at a time,
when the time is right. Each time, they help you achieve your immediate goal
by providing you the exact tool to do that. When you start a new game and
you're taught how to jump, a great tutorial doesn't teach you
<a href="https://www.youtube.com/watch?v=-f_2GmeMzIo">all the dozen ways you can jump</a>
at once but rather teaches you exactly the one jump you need at that point.
</p>
<p>
After that, you can experiment with yourself with different jumps (or by
analogy, ways to run the software) and explore to find new exciting things or
you can follow the tutorial to continue on the set path.
</p>
<p>
By being goal-oriented, the tutorials help the player avoid being overwhelmed
and confused. Instead of telling the player "here are all the things you can
do in the game, good luck figuring out what you actually need to do here", the
game guides the player to solving an actual problem with the smallest subset
of tools needed.
</p>
<p>
In tech, a feature-driven documentation focuses on all the individual features
or capabilities of a system: maybe it's the modules or functions or commands
in your software and it aims to build your understanding of the usage in a
holistic way and leaves the application of that understanding as a homework
for you.
</p>
<p>
Scott Sehlhorst has a great blog post about
<a href="https://tynerblain.com/blog/2006/10/09/goal-driven-documentation/">Goal-Driven Documentation</a>
that has a similar approach to the video game tutorials. Instead of focusing
on what the implementation of the software is like or what the user interface
is like, it focuses on individual use cases that achieve a distinct goal that
the developer has.
</p>
<blockquote>
<strong>Scott Sehlhorst: </strong>We write documentation so that people can
more easily do the job, not so that they can use the tool.
</blockquote>
<p>
Each use case focuses on only the things that matter to it and even more
importantly in my opinion, provides the right options (or at least smart
defaults) for the specific mission.
</p>
<p>
One documentation that I enjoy a ton that does a marvelous job with this is
<a href="https://tldr.sh/">tldr pages</a>. It's a command line tool that gives
you a list of community curated use cases and commands to achieve them:
</p>
<pre><code class="language-bash">➜ tldr zip
zip
Package and compress (archive) files into zip file.
See also: `unzip`.
More information: <https://manned.org/zip>.
- Add files/directories to a specific archive ([r]ecursively):
zip -r path/to/compressed.zip path/to/file_or_directory1 path/to/file_or_directory2 ...
- Remove files/directories from a specific archive ([d]elete):
zip -d path/to/compressed.zip path/to/file_or_directory1 path/to/file_or_directory2 ...
- Archive files/directories e[x]cluding specified ones:
zip -r path/to/compressed.zip path/to/file_or_directory1 path/to/file_or_directory2 ... -x path/to/excluded_files_or_directories</code></pre>
<p>
In the snippet above, tldr pages offers me few practical and specific ways to
use <code>zip</code> command. It doesn't abstract them away nor offer multiple
options for each but it's a mapping of one use case to one command.
</p>
<h2 id="allow-the-community-to-participate">
Allow the community to participate
</h2>
<p>
As long as there has been video games, there has been guide books and
walkthroughs and these days, a lot of games have community written wikis that
help players find information, solve problems and learn new things.
</p>
<p>
It has grown even further with the emergence of Youtube: for the popular
games, you can find video solution to almost any problem you face in the game.
And quite a lot of videos showing things you didn't even know you wanted to do
in the game.
</p>
<p>
These being done by the players is great because a player approaches the
problem from a very different viewpoint than the developer of the game because
they discovered the solution without knowing it before hand.
</p>
<p>
For a tech team, the imagination and the resources of the developer team are
always limited and it's impossible for one person or one team to come up with
all the possible examples and use cases in your tutorials.
</p>
<p>
A great way to expand your technical documentation from these guided and
narrow tutorials is to open up the reference documentation for your developer
community.
</p>
<p>
Developer communities are great for that as well since developers tend to like
to share and help each other. A great example of that is PHP's reference
documentation. On each page (like for example,
<a href="https://www.php.net/manual/en/function.rsort.php">this rsort page</a>) has the official documentation and below that, User Contributed Notes
section where community members can offer more snippets, discuss and help
other people to learn more and learn specific usages that grow the value and
usefulness of the documentation.
</p>
<h2>Related writing</h2>
<ul>
<li>
<a href="https://passo.uno/video-game-manuals-docs/">What tech writers can learn from video game manuals by Fabrizio Ferri
Benedetti</a>.
</li>
</ul>
Syntax Error #5: Python breakpoints
2023-07-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-5-python-breakpoints/
Syntax Error is a newsletter about debugging for developers, students, hobbyists, curious and duck fans.
This fifth issue talks about debugging Python and spesifically what I learned recently about using different debuggers in Python.
Read full article at <a href="https://www.syntaxerror.tech/syntax-error-5-python-breakpoints/">ssyntaxerror.tech/syntax-error-5-python-breakpoints/</a> and either subscribe to the email or RSS feed to catch all of them.
Hey dev student: you should study creative writing
2023-07-12T00:00:00Z
https://hamatti.org/posts/hey-dev-student-you-should-study-creative-writing/
<p>One of the key skills in modern worklife is the ability to communicate with other people. Communication itself is a vast field of skills but for software developers, a crucial one is the ability to write. This is becoming even more important in modern remote work life where your teammates, stakeholders, clients and partners can be all around the globe.</p><p>Over the past few years, I've been working with a lot of students through various roles: from the perspective of teaching programming, doing employer branding for a software company and from running developer communities. I've been half jokingly said often that if I could decide what to change in IT education, I'd add a compulsory creative writing class to the curriculum. As I think back to my own studies at the university, I mostly remember writing UML and ER charts and that one project course report that was half technical documentation and half academic writing. And the bachelor's and master's thesis writing that I never finished.</p><h2 id="what-do-we-devs-write-about-then">What do we devs write about then?</h2><p>First and maybe the most obvious one that springs to mind is the code. Work life has taught me one important thing about code: we spend way more time reading the code than writing it so it's not enough that a computer can understand your code. </p><blockquote>"Programs must be written for people to read, and only incidentally for machines to execute." - <a href="https://www.goodreads.com/quotes/9168-programs-must-be-written-for-people-to-read-and-only">Harold Abelson</a></blockquote><p>Naming your classes, functions and variables isn't just a technical exercise but a creative process where we aim to connect the real world equivalents to abstract code.</p><p>In addition the code, we write quite a lot of other stuff too: commit messages and pull request descriptions (or equivalents in your version control systems), bug reports, feature descriptions, internal process descriptions, wiki and intranet content, readme files and who knows what else. Technical in nature but creative as its aimed for a human reader nonetheless.</p><p>And to keep going, we write Slack/Teams messages, emails, comments, instructions, public documentation, meeting memos, briefs and reports.</p><p>Some developers even end up writing social media content, blog posts or newsletter content – whether as part of their job or as a hobby.</p><p>That's quite a lot of writing, given how little we spend time teaching and learning it as part of our studies.</p><h2 id="sell-your-ideas">Sell your ideas</h2><p>In a perfect world, one could argue that the best idea or viewpoint always wins. We do however live in an imperfect world where there's unlimited amount of ideas and extremely limited amount of time.</p><p>When we want to win people to your side, we need to succeed in communicating our ideas clearly and in a way that is well reasoned and inspiring. It could be high level ideas like new products or features but also much more day-to-day things like the way your team operates or bringing in new libraries and technologies to a project.</p><p>By putting effort into good documentation, you make it easier for both you and your team in the long run. With a ton of experience, I can confidently say that it's way nicer to start working on a bug when you have a bug report that's well written and documented. </p><p>The things you know at the moment of writing will escape your memory faster than you think so it's crucial to write them down or you and your team will end up wondering what does it mean that the "reader doesn't work" six months down the road.</p><p>Good storytelling skills are valuable in work life. And even if you never probably end up writing poems or novels at your job as a dev, skills in language, storytelling and communication learned through creative writing will go a long way to improve your changes at succeeding in your career.</p><h2 id="how-to-get-better-at-writing">How to get better at writing?</h2><p>In addition to aforementioned creative writing classes and reading prose, I highly recommend starting a blog. I have previously written <a href="https://hamatti.org/posts/you-should-start-a-blog-today/">why you should be writing a blog</a>, <a href="https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/">how blogging can be an effective refactoring tool</a> and <a href="https://hamatti.org/posts/writing-documentation-is-a-great-tool-to-improve-software-quality/">how writing documentation can improve you code quality</a>.</p><p>Blogging about technical things hones your skills to tell stories, to explain technical concepts in an easy-to-understand way and to sell your ideas. As an added benefit, it builds your credibility, visibility and personal brand which can lead to better work opportunities in your career.</p><p>Another thing is to read other people's technical writing. As of today, I'm subscribed to over 120 technical blogs RSS feeds and I regularly read documentation for open source libraries and commercial products to gain understanding how to explain things better.</p><p>What comes to writing documentation, Carolyn Stransky has a great talk about <a href="https://www.youtube.com/watch?v=15BSAwDLklE">Humanizing your documentation</a> which has fantastic pointers towards writing more effective and human-centric technical content.</p><p><a href="https://www.writethedocs.org/">Write the Docs community</a> has a ton of great resources from what tools to use and how to build documentation culture, to a vivid Slack and meetup/conference community of technical writers.</p>
Give your commands consistent names
2023-06-21T00:00:00Z
https://hamatti.org/posts/give-your-commands-consistent-names/
<p>
A lot of modern software development involves running different kind of
scripts or commands. If you're a Javascript developer, you might use
<code>npm run dev</code> to start your development server; if you're a Rust
developer, you may use <code>cargo build</code> to build your project; a
Django developer may run <code>python manage.py test</code> to run tests.
</p>
<p>
Giving these things consistent names across your projects (within your team,
organization or your personal projects) and wrapping them with tools like
<a href="https://www.gnu.org/software/make/">make</a> or
<a href="https://github.com/casey/just">just</a> can make a big difference in
helping the team remember both the commonly used and the rarely run commands.
</p>
<h2 id="discussion-around-the-topic">Discussion around the topic</h2>
<p>
The idea for this blog post originated from
<a href="https://mastodon.social/@mhoye/110544320436786292">mhoye's toot</a>:
</p>
<blockquote>
The most common thing I want to say to GitHub projects is "Please use
Make".<br /><br />It's old as dirt and the syntax isn't great, but even in its
simplest use being able to reduce a wall of shell copypasta to "make whatever"
is such a breath of fresh air.<br /><br />Get yourself to where "install",
"install-devenv", "build" and "run all my tests" are all dead easy, zero
chaff, no typos simple. And if you find yourself re-using some long command
chain, add it to the makefile and you're done. It is so good.
</blockquote>
<p>
Greg Wilson shared
<a href="https://third-bit.com/2023/06/13/making-a-book/">his set of commands he's using when he's writing book projects in his
blog</a>.
</p>
<p>
Through that discussion, I also found GitHub's
<a href="https://github.com/github/scripts-to-rule-them-all">Scripts To Rule Them All</a>
approach that resonated a lot.
</p>
<p>
There's more discussion and shared approaches in the discussion section of
mhoye's original toot so I recommend checking that out.
</p>
<h2 id="many-tools-wrapped-in-one">Many tools wrapped in one</h2>
<p>
In my current job, a colleague has built a custom command line tool that wraps
all the various scripts and commands we use within a singluar, well-documented
user interface.
</p>
<p>
We use it to manage docker containers, node processes, different tests (with
automatic recognition of which test runner to use based on the test path or
file being run) and so on. Under the hood, they all use wildly different tools
and I'd be the first one to admit I'd forget the commands all the time if I
had to remember them. And as a bonus, we don't need to remember in which
docker container we need to run certain commands as all that is hidden into
the command line tool implementation details.
</p>
<h2 id="naming-is-documentation">Naming is documentation</h2>
<p>
One benefit of this approach is that you need to remember way less commands,
especially if working in an environment that has different programmin
languages and ecosystems in use. If you can run <code>make test</code> every
time instead of remembering <code>npm test</code>,
<code>python manage.py test</code> and <code>cargo test</code> and which one
to use when, you have one less thing to worry about.
</p>
<p>
Another benefit is that giving names to things works as documentation. Command
line commands are often very low-level implementation oriented so giving them
names that mean something in <em>your context</em> packs a punch.
</p>
<p>
So far I've personally been doing this within projects (for example I use
<code>make</code> for my
<a href="https://github.com/Hamatti/ptcgo-card-viewer-extension/blob/main/makefile">Pokemon TCG Card Viewer Firefox extension</a>) but this discussion sparked the idea to start using this approach more
across my personal projects in the future.
</p>
Document outdated components
2023-06-14T00:00:00Z
https://hamatti.org/posts/document-outdated-components/
<p>One of the realities of code bases that they don't teach you at school is that they evolve, often bit by bit. What that means is that there's rarely a "we decided to start doing one thing differently, let's rewrite everything to match that" moment – no matter how often we developers dream of being able to do that.</p><h2 id="document-design-via-storybook">Document design via Storybook</h2><p>Documenting visual elements and design is important to help maintain a consistent visual style and to make sure you use the right components at the right places when building your application.</p><p>This is especially important when there's no strong design oversight during the review process (for example, no designer in a team). I've seen it with my own eyes when an app ends up in a situation where there's 5 different <code><table></code> styles across 7 different sub pages of the same tab group.</p><p>All of that because 1) the developer had to pick one and copy it as an example or design one from scratch and 2) because nobody caught it during review.</p><p>But if your team is just like so many other software teams in the world, you might not have that.</p><p>In a case like that, building and documenting a design system using something like <a href="https://storybook.js.org/">Storybook</a> is a great way. You could either mark the old components as deprecated or not document them at all, maybe marking them as something that should be brought up to speed with the rest whenever there's time to refactor.</p><p>Storybook has become one of my favorite tools lately as it allows you to document the visual and functional side of your application. With one look, without ever opening the code editor and trying to guess, you can find what kind of options you have for base components and more complex, specific components built on top of them. And you can tell the viewer <strong>what they mean, when they should use them and why they are like they are.</strong> That's brilliant, if you ask me.</p><p>You can document the different props given to a component and how they affect the visuals and function of a component. The developer can then go to their code editor and import the right component and pass the right props to it. And when creating new components, less guesswork and less opportunities to accidentally pick up an older deprecated one as a template.</p><h2 id="iterative-improvements-in-coding-style-can-lead-astray">Iterative improvements in coding style can lead astray</h2><p>This also applies to coding style.</p><p>Let's say a team started working with React when class based components were all the recommended way to build components and they built a notification component for their news:</p><pre><code class="language-js">import React, { Component } from 'react
class NewsNotification extends Component {
render() {
<span class="notification-badge">{this.props.newsNotifications.length}</span>
}
}
function mapStateToProps(state) {
const { newsNotifications } = state
return { newsNotifications }
}
export default connect(mapStateToProps)(NewsNotification)</code></pre><p>Now, let's say some years later the team shifted their development style to use function components and the next time they built a notification component for messages, they did it differently:</p><pre><code class="language-js">import React from 'react'
import { useSelector } from 'react-redux'
const MessageNotification = () => {
const messageNotifications = useSelector(state => state.messageNotifications)
return <span class="notification-badge">{messageNotifications.length}</span>
}
export default MessageNotification</code></pre><p>Now, let's say the team hires a new developer and one of the early tasks is to create a notification component for a third type of notification in the system. A usual way they'll start looking into it is to look for something similar in the app, find it in the code and then use that as a blueprint.</p><p>Given that the system has two notification systems, it's roughly 50/50 which one they end up looking up as the example. Or they might build something that is bit more complex than our example based on that example and have 50/50 chance of picking the right one. Then, maybe in the code review, they hear the "oh, you should do it like this". Or worse, nobody picks it up during the review and the code base ends up diverting further and further apart.</p><p>It's common that these older files are refactored iteratively bit by bit – those of us who have been in projects that attempt large rewrites know that they very often lead to nothing getting finished because there's always something to fix.</p><p>In those cases, it's important to document this somewhere. Maybe in those files that your team has identified as future cases, you can add a comment that says</p><blockquote>When you change this file the next time, refactor it to use [current style]</blockquote><p>This has two benefits: first, it's less likely that this style gets copied to a new file and second, you are reminded to refactor it when you make other changes to the file.</p><h2></h2>
How to network while being beneficial to oneself professionally?
2023-06-07T00:00:00Z
https://hamatti.org/posts/how-to-network-while-being-beneficial-to-oneself-professionally/
<p>There's <a href="https://workplace.stackexchange.com/questions/189237/how-to-network-while-being-beneficial-to-oneself-professionally">a great question in workplace.stackexchange.com about networking from Dec 2022</a> (I'm quoting the original question here, not the edited-by-another-user version):</p><blockquote>First thing first, I'm a engineer/scientist in industry.<br /><br />While the way of professional networking has been portraited as 'helping others', for example by supporting them professionally/make them look good etc., and have wishful thinking that someday in some point of time, the goodwill would be returned back to me. (I'm thinking it similar to consciously aware of being used but sincerely follow suit).<br /><br />However, I do dreams of my own (e.g. build up my expertise in the field, for example). And by doing what I just mentioned (to be available as a tool for other, perhaps those up the hierarchy), I sacrifice my energy and time that I could have otherwise invested in building up my profession.<br /><br />On risk analysis, the latter option always seem to be the one with lowest risk as I can never guarantine the goodwill would ever be returned. And it's not rare to see bosses would just use juniors to achieve what they want without bothering about their career growth/prospects etc.<br /><br />In addition, as a specialist, I can't adopt the ordinary way how generalist network: they are willing to do whatever work to climb up, but I am not willing to go outside of my area of expertise (but can be a manager in the field). Thus, networking could bring more harm than good to me if I don't do it right, as I would run the risk of deviating from building expertise A LOT.<br /><br />But I do believe the power of networking. But I'm a bit confused: are there ways that get the good of both world? How can I network and at the same time advancing my professional capability?</blockquote><p>There are already some answers in that thread but the question intrigued me since I think it shows some very common ideas about networking that I wanted to address as well so I wanted to write a reply in form of a blog post for the larger audience.</p><p>I think the person asking the question has picked up many of the good bits and pieces about networking but connected them in a bit wrong order.</p><p>Here are the highlights that I agree with:</p><ul><li>Help people learn new things, connect with new people and achieve their goals</li><li>The "pay forward" mentality: don't expect a direct return of favor if you help someone</li><li>Building their own expertise and professional connections</li></ul><p>However, what I think they got wrong is that these things would be separate from each other.</p><h2 id="help-others-learn-connect-them-to-others">Help others learn & connect them to others</h2><p>I've found the best way to network is to build relationships and work with people who share your interests. It's even better, if you find people from different "stages" of career in it: some just beginning, some at your peer level and some seasoned experts with more experience than you.</p><p>You can learn from each group: </p><ul><li>with the <strong>more experienced people</strong> you can learn from the experience they have that you still lack and learn about the unknowns. </li><li>with <strong>people earlier in their career</strong> you'll often learn about the new things and where the world and the industry is moving – and you can improve your core skills by helping them learn those. </li><li>with <strong>your peers,</strong> you can often find a lot of very practical tips and as well as mental support as they are going through the same as you.</li></ul><p>Your network also isn't just the first degree connections (the people you know directly). It's also the 2nd and even 3rd degree connections (who the people you know, know). One of the most powerful methods of growing your network is introducing people you know to other people you know or meet. When those people end up doing new things or meeting new people, they are very likely to bring you in and introduce you further because you are a common connection.</p><h2 id="pay-forward">Pay forward</h2><p>The mentality of paying forward isn't so much about "wishful thinking that someday in some point of time, the goodwill would be returned back to me" as the original question put it. I'd rephrase it as <strong>"don't help others by expecting (and keeping tally of) immediate direct return"</strong>.</p><p>The key reason for that is that it limits you too much. If you go to every interaction thinking "what can they do for me in return", it's very hard to find cases where you would find a perfect win-win return.</p><h2 id="build-your-own-expertise">Build your own expertise</h2><p>The gist is to do both of the above within your expertise. As the original question was asked by an engineer in the industry, I use that as an example:</p><p>If you connect with people who work in the same industry as you, share with them what you know, help them find jobs or interesting projects, let them know of interesting opportunities and maybe even mentor juniors and be mentored by seniors, you'll build a lot of social capital: relationships where people know you, trust you and know very well what your expertise and skills are.</p><p>People who know you and what you're interested in, are the people who can let you know of the opportunities and those are the people who'll mention your name when some 3rd party is wondering who'd be a great fit for a new project or case.</p><p>In the software industry for example, a lot of networking happens in meetups and conferences and online communities where the core part is people sharing their expertise. Let's say you give a talk in a 2-day conference. That's broadcasting your expertise to a few hundred people while helping them learn. For the rest of the conference, you can learn from dozen other speakers' talks, deepen your knowledge in your own topic by discussing it with other participants after your talk and so on.</p><p>You don't need to separate your own learning from helping others – the best is when both happen at the same time. Helping others also positions you as the expert in their eyes, which in turn opens up a lot of opportunities in your career.</p>
Writing documentation is a great tool to improve software quality
2023-05-31T00:00:00Z
https://hamatti.org/posts/writing-documentation-is-a-great-tool-to-improve-software-quality/
<p>I had a great day at work yesterday. I'm on the final stretch of developing a larger feature and as part of it, I had written code to make it feature gate-able. To figure out how to do that, I looked at other similar parts, tried to put 1 and 2 together and bit by bit managed to make it work.</p><p>At that point, I felt the urge to document this workflow since I was certain this wasn't the last time we'd add a gated feature and I wanted the experience to be more straight-forward for the next person: whether that would be a team mate or future me.</p><p>I have earlier written about how <a href="https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/">blogging is my favorite refactoring tool</a> and documentation is in the same realm. It forces me to look at the code from a different perspective.</p><h2 id="learning-how-the-system-works">Learning how the system works</h2><p>I would encourage every junior developer to work on documenting what they learn at work – whether it's on a more general level on your own blog or even better, very detailed in your work project. It forces you to dig deeper and understand the underlying system, helping you ask great questions from more senior developers and gaining better understanding of the code base.</p><p>I started my documenting project yesterday by taking a look at one of the permission models: I looked at all the different options and values you can give to the configuration, looked into how they were used in the project and built understanding for what the options are and how they work.</p><p>I then added documentation about the components and functions that one can use to limit what can be used based on the gates. At the end, I think I have a good documentation that someone can follow and get things done and have easier time even customizing it to their liking when options are explained.</p><p>I even added a few test skeletons at the end that developer can use to see how to write tests for these feature gates because I had a bit of trouble figuring it out initially.</p><p>Being the one who writes the documentation also exposes you to discussions with other people in your team who have originally built the features as you figure out why certain decisions have been made.</p><h2 id="surfacing-bugs-and-improving-quality">Surfacing bugs and improving quality</h2><p>During my project, I discovered 5 bugs without writing a single line of code – simply by gaining understanding of the code and reading it with the mindset of "I need to explain this to someone else". 3 of them were existing bugs that hadn't been reported and 2 were potential bugs.</p><p>One being a case where to trigger the bug, it would require a regression to happen elsewhere in the code but since we were dealing with permissions, it could have led to some features leaking as a result to users.</p><p>The other one was a case where there was nothing technically wrong but when I was reading and writing, I realized that the wording of the interface gave me a stricter understanding of permissions than was implemented -> if someone else in the future had the same understanding, they might have introduced a bug.</p><p>It's interesting that the only reason I noticed these was because I was in the writing mode. It's such a powerful tool.</p><h2 id="surfacing-inconsistencies">Surfacing inconsistencies</h2><p>Another thing that you start to notice when writing is code style inconsistencies like naming.</p><p>For example, let's say that we'd have route guards and most of them are called <code>requireAlphaPermission(field)</code> or <code>requireBetaPermission(field)</code> and then one of them is called <code>checkGammaPermission(field)</code>. They all do the functionally same thing for their own category.</p><p>Now, there's nothing wrong with either of these verbs. Both of them in isolation can mean the same thing and was probably the case that they were developed in different cases over time and not thought about that much. It's hard to notice the big picture around when focusing on feature development but they are so much easier to notice when writing and bringing them up as examples.</p><p>Consistent naming in case like this helps the code base because it makes it easier for the developers to have more intuitive understanding of what the functions do.</p><p>This kind of thing is also a great moment to have a discussion with your team or at least someone who maybe originally wrote the code or knows that part of the code best. My approach is always "Is there something I should know about the reason behind this? I'm thinking X but maybe there's history and decisions that I'm not aware of so let's work it out so I can document it properly (and change if it is indeed a bug or an oversight)".</p><h2 id="it-s-never-finished">It's never finished</h2><p>Documentation is always a living organism: I expect team mates to make notes, add things and help make it better over time as our collective understanding improves.</p><p>My goal with documentation has multiple goals: </p><ol><li>It helps me learn the system</li><li>It helps the next person implementing it</li><li>It helps people learn and make better solutions</li><li>To document the silent knowledge in a growing team</li></ol>
I found new great games
2023-05-24T00:00:00Z
https://hamatti.org/posts/i-found-new-great-games/
<p>Last few weeks have been great on the gaming side of things.</p><p>First, <a href="https://www.ludonarracon.com/">LudoNarraCon</a>, my highlight of the gaming year, was organized. It is an annual digital convention for narrative-driven video games and over the past few years, I've found it to be my niche. Each year, I enjoy listening to talks and panel discussions and then end up buying a dozen new games that explore video games from fresh and new perspectives while telling amazing stories.</p><p>Over the years I've found fantastic games like <a href="https://store.steampowered.com/franchise/OrwellGame">the Orwell franchise</a>, <a href="https://store.steampowered.com/app/322450/FRAMED_Collection/">FRAMED Collection</a>, <a href="https://store.steampowered.com/app/501300/What_Remains_of_Edith_Finch/">What Remains of Edith Finch</a> and <a href="https://store.steampowered.com/app/828900/The_Stillness_of_the_Wind">The Stillness of the Wind</a>.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/ludonarracon-picks.png" class="kg-image" alt="LudoNarraCon logo on top and six game posters below it for Citizen Sleeper, Tails: The Backbone Preludes, Loco Motive, NORCO and Glitchhikers: The Spaces Between, and The Pale Beyond" /></figure><p>This year, I decided to pick up <a href="https://store.steampowered.com/app/1578650/Citizen_Sleeper/">Citizen Sleeper</a>, <a href="https://store.steampowered.com/app/2020030/Tails_The_Backbone_Preludes/">Tails: The Backbone Preludes</a>, <a href="https://store.steampowered.com/app/1709880/Loco_Motive/">Loco Motive</a>, <a href="https://store.steampowered.com/app/1221250/NORCO/">NORCO</a> and <a href="https://store.steampowered.com/app/1449230/Glitchhikers_The_Spaces_Between/">Glitchhikers: The Spaces Between</a> (and I would have picked up <a href="https://store.steampowered.com/app/1266030/The_Pale_Beyond/">The Pale Beyond</a> if I didn't already own it!).</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image-3.png" class="kg-image" alt="A purple and teal text logo of Loop with a purple square next to it" /></figure><p>And completely unrelated to LudoNarraCon, through <a href="https://indieblog.page/">IndieBlog's random blog feature</a>, I found a review for a puzzle game called <a href="https://store.steampowered.com/app/2346490/Loop/">Loop</a> that cost under 1 euro and decided to tease my brain with it. It's especially great for Steam Deck due to its simple mechanics and controls, short puzzles and the fact that it doesn't need a large screen.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image-4.png" class="kg-image" alt="The Legend of Zelda: Tears of the Kingdom poster with Link crouching on the edge of a sky island piece" /></figure><p>On Friday the 12th, the new and long awaited <a href="https://www.zelda.com/tears-of-the-kingdom/">Legend of Zelda: Tears of the Kingdom</a> got released. I did the responsible thing: bought it and set it to download in the morning, headed over to the office and totally not think about the game all day long until I got home. Once I did get home, the first 5-10 minutes proved that we have another masterpiece at hand. It's was one of those "If you're not careful, it's Monday morning real quick" and that was the story of my weekend.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image-5.png" class="kg-image" alt="Advance Wars 1+2 Reboot Camp poster" /></figure><p>Speaking of Switch games worth buying, last month also saw the launch of <a href="https://www.nintendo.com/store/products/advance-wars-1-plus-2-re-boot-camp-switch/">Advance Wars 1+2: Reboot Camp</a>. If you have never played the originals, I highly recommend it. I'm a huge fan and bought this one despite knowing that it was just the old missions with new graphics on a new system. But it's still good and now I can play those on my Switch so yay. I have recently been playing more of the <a href="https://www.romhacking.net/hacks/6012/">Advance Wars Returns rom hack</a> version though .</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image-6.png" class="kg-image" alt="Wingspan, relaxing strategy game about birds for 1 to 5 players" /></figure><p>To balance things out, I got introduced to <a href="https://store.steampowered.com/app/1054490/Wingspan/">the digital version</a> of the great <a href="https://boardgamegeek.com/boardgame/266192/wingspan">board game Wingspan</a> by a colleague and decided to buy it to myself too. Like always, there are a few UI challenges with the digital version but other than that, it works great. I like that you can play multiplayer on the same computer and that it works really well with controller, making it a great Steam Deck game too.</p>
I got my hands on the finished product (Potluck)
2023-05-17T00:00:00Z
https://hamatti.org/posts/i-got-my-hands-on-the-finished-product-potluck/
<p>Yesterday was a beautiful day. The mail arrived and with it, the physical cards for <a href="https://hamatti.org/tabletop/potluck">my Potluck deck</a>. As someone who has always mainly worked on digital products, there's a magical feeling when you get to physically touch and tinker with your own creation.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image.png" class="kg-image" alt="A deck with a few cards fanned out face-down, showing a dark blue background with white logo saying Potluck. Below them, five white cards face up showing numbers 1, 11, 21, 31 and 71 and a variety of other elements in the cards. Below them, a single card listing a bunch of games and credits for the project." /></figure><p>Potluck is so far the best project of its kind that I've made and I'm so happy by how it turned out. Compared to the older version (<a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Project 108</a>), I can immediately feel the improvements when just playing around with the cards.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/cards.jpg" class="kg-image" alt="Cards numbered 79 through 85 fanned in a way that only top left corner shows a number in white on a black circle." /></figure><p>The inconsistently running numbering on the top left corner was one of the biggest mistakes of the previous one and I'm so happy I got that fixed and every card has a number now on the top left, making it easier to find the right card.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image-1.png" class="kg-image" alt="On the left, card numbered 6 right way up and on the right card numbered 9 upside down. The font has drop shadow to the bottom right so numbers are distinguishable." /></figure><p>I think the font in the middle is a big improvement too. It's slightly smaller but with the black outline and shadow, I feel it's more legible (and it's no longer possible to mistake 6 for 9 or 69 for 96).</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/image-2.png" class="kg-image" alt="On left, card numbered 70 with Onitama frog pattern, text "$12,000" and three bull heads. On right, card numbered 39 with barely visible grey footprint, text "Thief: Steal 1 from each adjacent player" and 1 bullhead" /></figure><p>I added quite a few games and made improvements for games that were already playable with the old one. For example, above numbers up to 42 there are small footprints on a very light grey color: meant to be unnoticeable unless you need them as a reminder when playing Fugitive. I was bit worried how that would turn out in the print compared to how it looked like on the screen but it came out brilliant.</p><p>With new additions of games, like Onitama movement grids, I was able to also balance the cards and get most of out the deck. The last project had a lot of cards that almost only served the purpose of the card number and now that's no longer the case. There's an added 4 suits of playing cards and other than a very few cards at the higher up numbers, most cards have at least 4 different elements for different games.</p><p>As a bonus, I ordered an experimental side accessory for the project that hasn't arrived yet. To play Onitama, you need a 5x5 grid, so I created one and ordered a mousepad with custom image from a local photo shop. Once it arrives, I'll share how it looks and works like.</p><p>I can now finally retire my older project into my project hall of fame and replace it in my backpack with Potluck. And I get to showcase the actual cards instead of just the website when I talk to people about it.</p><p>A few people have asked how they could get their hands on some. I don't know yet. I might release the files with a <a href="https://creativecommons.org/">Creative Commons</a> license but before that I'd need to do a few things and haven't quite decided yet if I'll publish them. </p>
Syntax Error #4: Refreshing wrong window
2023-05-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-4-refreshing-wrong-window/
Syntax Error is a newsletter about debugging for developers, students, hobbyists, curious and duck fans.
This fourth issue discusses the case where you keep making changes to code and nothing changes in the app. There's a plethora of reasons that can cause it: looking at non-dev environment, forgetting to reboot dev server or build your binaries.
Read full article at <a href="https://www.syntaxerror.tech/syntax-error-4-refreshing-wrong-window/">ssyntaxerror.tech/syntax-error-4-refreshing-wrong-window/</a> and either subscribe to the email or RSS feed to catch all of them.
In a beautiful world, APIs would be open
2023-05-03T00:00:00Z
https://hamatti.org/posts/in-a-beautiful-world-apis-would-be-open/
<p>I briefly talked about my dislike for walled gardens in my blog post <a href="https://hamatti.org/posts/in-defense-of-quote-toots/">In defence of Quote Toots</a> and this past weekend has been a great reminder for myself why that is. Advertising as a main revenue model is one thing that causes a situation where it's more important that people see the information (and thus ads) <strong>in your website</strong> than for them to see the information.</p><p>I'm part of a few groups that are very passionate about hockey. This time of the year, that means we turn the knobs to the max as the NHL playoffs are on-going. For years, NHL has had <a href="https://bracketchallenge.nhl.com/en/home">a Bracket Challenge</a> (and before that, I participated in fan-made bracket challenges for over 10 years) that we create our own league for, put in our bets and then see who reigns supreme at the end of the spring.</p><p>There are just a few tiny problems with that...</p><h2 id="the-problem">The Problem</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/Screenshot-2023-05-02-at-11.26.22.png" class="kg-image" alt="A small portion of the image in the middle has a leaderboard table with a scrollbar and shows player name, champion pick, total points and total possible points. Not all entries are visible despite there being plenty of screen space." /></figure><p>The main problem is that when you open the league view, you are greeted with this tiny box in the middle of the screen, not even showing all the players (we only have 16!) at once. Plus it shows me as the user always on top.</p><p>What I love to do every time a series ends is to post an update to our group Slack to bring a bit of story and hype to the series. It's really hard to get a good screenshot of that page.</p><p>Earlier years I was able to get around with custom CSS but this time it's lazy loading on scroll and once it loads, it appends, not replaces the data and a few other annoyances.</p><h2 id="the-fix">The Fix</h2><p>So last Friday after attempting to make the first update, I decided to build my own. Juho found that the APIs are open so I built <a href="https://kk-bracket.netlify.app/">a quick UI</a> to display everyone's picks and points. This enabled me to share again with the group!</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/05/bracket-blurred.jpg" class="kg-image" alt="A website with a table that shows 16 rows of players, their accumulated points and their picks, either correct or incorrect for 8 rounds of NHL playoffs." /></figure><p>As days of the weekend went by, I added features, tweaked the functionalities and styling and had at least one other person pick up this code and use it for their own league!</p><p>It's not the most beautiful piece of art ever – I'm definitely not a designer – and it breaks horribly on mobile.</p><p>But it's one thing that open APIs would enable us to do: build alternatives, things that fit our needs and our desires instead of being blocked inside a walled garden.</p><p>I'm not sure if NHL planned for those APIs to be open so every morning I wake up to two worries: did my code break and did they close the APIs. So far, neither has happened.</p><p>As usual, I put the code out there in the <a href="https://github.com/hamatti/kk-bracket">open with a permissive MIT license into my GitHub</a>. Maybe someone else picks it up and uses it to their own league. I already got a helpful contribution to make it usable on mobile.</p><p>Very likely this code won't work anymore next year but I'm hoping it survives until the end of these playoffs.</p><h2 id="imagine-if-it-was-all-like-this">Imagine if it was all like this</h2><p>I keep dreaming of the world where you'd have open APIs and open data and people could build their own imaginative projects on top. Different users have different needs and we could have a wide variety of tools, apps and UIs around the same data.</p><p>And you could then combine other sources of data too. Maybe we could spice up the bracket with highlight videos from Youtube or playoff stats from another API. Or showcase previous years' bracket scores. The sky is the limit.</p><p>But too often, the only thing that matters is getting people to your website so you can show ads and the actual content and data and competitions are just a lure to hook us into watching those ads.</p><p>And that makes me sad sometimes.</p>
How to document a workaround?
2023-04-26T00:00:00Z
https://hamatti.org/posts/how-to-document-a-workaround/
<p>When writing code, every now and then we end up in a situation where something outside of our influence isn't working properly. It could be a library we're using, a browser we are targeting or an integration to 3rd party service we're relying on.</p><h2 id="workarounds-are-hacks">Workarounds are hacks</h2><p>That's when we need to write a workaround. A dreaded <em>hack</em> that solves the original problem in a different way than originally intended. As one example, I recently <a href="https://hamatti.org/snacks/firefox-extension-storage-bug-workaround/">wrote about an issue in Firefox with dev tools and extensions</a>. I learned a way around and implemented that while waiting for the bug to get fixed, which it now has been.</p><p>A workaround has a few important characteristics:</p><ol><li>Its job is to help you keep your application working and functioning</li><li>It's meant to be a temporary solution with as short lifetime as possible</li><li>It <strong>should not</strong> be refactored away while the original problem exists</li><li>It <strong>should </strong>be refactored away as soon as the original problem has been fixed</li></ol><p>These make it very important that we document the workarounds well. Let's learn how!</p><h2 id="document-very-close-to-the-code">Document very close to the code</h2><p>For reasons #3 and #4 above, it's important to keep the documentation as close to the code as possible. In practice, this means writing a good code comment next to the workaround.</p><p>This is the perfect case for when code comments make a ton of sense. (By the way, if you wanna learn about other good uses of code comments, <a href="https://www.youtube.com/watch?v=yhF7OmuIILc&pp=ygUUYXJ0IG9mIGNvZGUgY29tbWVudHM%3D">Sarah Drasner's great talk Art of Code Comments from JSConf Hawaii 2020</a> is a great watch.)</p><p>If we don't document it close to the source (for example, if we write it to intranet, wiki or bug tracker somewhere), we risk a developer later looking at it, noticing there's something weird there and attempt to refactor it, possibly introducing a bug.</p><p>So let's write our first attempt:</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">// This is a workaround for a Firefox bug
browser.storage.local.onChanged.addListener(() => {});</code></pre><!--kg-card-end: html--><p>Now we've recorded that the code that looks out of place (other than its bug-fixing side effect, it's basically a no operation, empty code that doesn't do anything) is there for a reason.</p><p>Without a comment, a lot of developers might end up in one of the two bad situations:</p><ol><li>They might delete the line of code because it functionally does <strong>nothing</strong></li><li>They might be afraid to delete it because they don't know why it exists</li></ol><p>Both cases are bad because the first leads to needed code being deleted and the second leads to possibly unneeded code to exist and rot in the codebase.</p><p>I'd argue that our comment is not great however. Even though it does answer the question "why?" (which is a sign of a great comment), it does not help with the other parts. It doesn't explain what the wrong behavior is that it fixes and it doesn't help us figure out when it becomes obsolete.</p><h2 id="add-references-to-other-sources">Add references to other sources</h2><p>As I mentioned earlier, this is code we <strong>want to get rid of </strong>as soon as possible. It's not robust and it's a hack and once it's not needed anymore, it's causing extra cognitive load to those who read it and possibly a performance hit when its run. In this example case that performance hit is tiny but for other workarounds it might be a big trade off.</p><p>In addition, it adds unnecessary complexity to our code, making future modifications and improvements more difficult.</p><p>To improve the comment, I'd do this:</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">// There is a bug in Firefox developer tools where storage display doesn't update correctly unless there's a listener attached.
// This bug is tracked in Bugzilla https://bugzilla.mozilla.org/show_bug.cgi?id=1802929.
// Once it's fixed, this line of code can be safely removed.
browser.storage.local.onChanged.addListener(() => {});</code></pre><!--kg-card-end: html--><p>By linking to a bug ticket, discussion, documentation or blog post helps anyone who encounters this code in the future. They'll get prompted "hey, you might wanna check out if this is needed anymore" and we can safely remove the workaround code once we can confirm it's fixed.</p><p>It's even better if you can subscribe to updates on the bug tracker to a shared email or Slack channel so you'd get immediate notification once situation clears.</p><p>I also like to be very explicit in this comment:</p><ol><li>What is the bad behavior it fixes?</li><li>Where can you find more information about it?</li><li>An instruction to remove it (ie. nothing else is relying on this)</li></ol><h2 id="what-if-there-s-no-clear-easy-source-to-refer">What if there's no clear easy source to refer?</h2><p>Sometimes, there's no clear or public ticket tracker for these kinds of bugs. Usually though, if you found out that it is a bug in the other system/library/tool, you discovered it <em>somewhere. </em>Document that. A blog post, a discussion thread in the bird site or the orange site or whatever it is.</p><p>Even if we can't be sure that the original source gets updated as the bug gets fixed, it usually gives us a better starting point to search further.</p><p>You can also add mentions of multiple sources, if you ran into them during your initial discovery. It's way easier to find them when you discover the bug, compared to 2 years down the line if the only thing you have at that point is "this is a workaround, don't touch".</p><p>It doesn't have to be perfect. Anything you have that you went through during your research while creating that workaround is good. You could also document in there why/how this particular workaround works and fixes the issue.</p>
A look at my on-going projects
2023-04-19T00:00:00Z
https://hamatti.org/posts/a-look-at-my-on-going-projects/
<p>I realized at some point that I really enjoy reading developers' blogs where people share the progress they've made on their projects, even if I'm not using them. And at the same time, I'm very shy to talk about my stuff because it feels a bit pushy. I decided to collect my on-going projects into this one piece.</p><p>I'll start with the community stuff and then move on to the software projects that I build and maintain.</p><h2 id="syntax-error-a-newsletter-about-debugging">Syntax Error: A newsletter about debugging</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-8.png" class="kg-image" /></figure><p>This Monday I sent the third newsletter to 163 email subscribers and who knows how many RSS subscribers and I'm very happy with the start that <a href="https://www.syntaxerror.tech/">Syntax Error</a> has had. Syntax Error is a monthly newsletter where I share everything I know about debugging software - both from technical and mindset perspectives.</p><p>It's been heart warming to get responses and feedback from readers, to see people recommend it to others and I was very happy to see it being mentioned in the <a href="https://www.elmweekly.nl/p/elm-weekly-issue-259">Elm Weekly #259</a>.</p><p>If you're a developer or a student of software, I can highly recommend heading over to <a href="https://www.syntaxerror.tech/">https://www.syntaxerror.tech/</a> and subscribing! I still have plenty of tips and stories to share.</p><h2 id="turku-frontend">Turku ❤️ Frontend</h2><p>Our <a href="http://turkufrontend.fi/">Turku ❤️ Frontend</a> community is doing great and we've been on a good momentum of monthly events since the pandemic restrictions were lifted a year ago. This May will see our 50th meetup and I'm more excited than ever about the future.</p><p>Turku ❤️ Frontend is a developer community for anyone interested in frontend development. We bring together professionals, students, hobbyists and curious to monthly meetups and other events that promote positive developer culture. In addition to the formally organized program, we also offer a way for the community to chat with each other, to organize afterworks or lunch gatherings and make new friends and find new jobs in the industry.</p><h2 id="pokemon-tcg-online-to-live-deck-list-converter">Pokemon TCG Online to Live deck list converter</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-3.png" class="kg-image" /></figure><p>Pokemon is in the middle of sunsetting their older Online client and promoting their newer (albeit rather buggy and slow) Live client. Both of these operate on same cards and both offer an import/export functionality for decklists.</p><p>But they are not compatible with each other...</p><p>A decklist exported from Online does not work properly when imported to Live even if all the cards would be supported.</p><p>After an afternoon discussion with the community about the issue and realizing that even though Online won't be around for long, there's still gonna be a ton of PTCGO formatted decklists in the web, I decided to fix it.</p><p>I picked up the codebase for my other app (see GLC Decklist Validator below) and wrote a few new lines of code (and just commented out others, the codebase is a mess right now) and suddenly I had a wonderful <a href="https://ptcgo-to-ptcgl.netlify.app/">web app that takes in a PTCGO decklist and prints out a PTCGL decklist</a>.</p><p>Big thanks to community member Subject00147 for doing the hardest part and researching what the incompatibilities were and what needed to be fixed. I just coded those into an app.</p><h2 id="firefox-extension-pokemon-tcg-card-viewer">Firefox Extension: Pokemon TCG card viewer</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-4.png" class="kg-image" /></figure><p>First one is my Pokemon TCG card viewer extension for Firefox that can be installed from <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/">addons.mozilla.org</a>. I originally built it as my learning project last year when I joined Mozilla. If I may say it myself, it's quite fantastic tool for what it does.</p><p>It lets Pokemon TCG players see the image of the card when they are referred to with a cryptic Pokemon TCG export format (like Bidoof BRS 120). After the initial work, I've been updating it with small bug fixes and mostly with new sets as they arrive.</p><p>With Pokemon moving towards Live as their flagship digital client, I had to make a quick hacky fix lately to support the newest Scarlet & Violet set and I still need to add full support for Live export codes in addition to Online ones as they changed a few things there.</p><p>I've also been considering making a Chrome version but that's waiting for a longer stretch of time when I'd update the entire extension first to support manifest V3 and then making it compatible with both browsers.</p><h2 id="gym-leader-challenge-decklist-validator">Gym Leader Challenge Decklist Validator</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-5.png" class="kg-image" /></figure><p>60 card singleton decks in a restricted format can be a hassle to validate. That's why 1.5 years ago I built <a href="https://glc-checker.netlify.app/">GLC Decklist Validator</a> that takes in a PTCGO decklist (with added support for the new Scarlet & Violet set) and validates all the important aspects of the list to see if it's legal to be played.</p><p>Over the <a href="https://glc-checker.netlify.app/changelog.html">past 20 months</a>, I've been fixing bugs, adding support to new sets and improving the validations to cover more cases.</p><p>The digital state of GLC is in bit of turmoil right now as the older Online client doesn't support the newer sets but the newer Live client doesn't support the older sets. I'll still keep updating and improving the validator so it can be used with a bit of manual decklist building.</p><h2 id="firefox-extension-pokemon-tcg-online-code-helper">Firefox Extension: Pokemon TCG Online Code Helper</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-6.png" class="kg-image" /></figure><p>Talk about low-maintenance apps! <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-online-code-helper/">Pokemon TCG Online Code Helper</a> has not needed a single update since launch as it keeps on giving. It helps Pokemon TCG players to manage their Online or Live booster code imports by making it easier to copy-paste the codes from their purchases and keep track of what has already been redeemed.</p><h2 id="235">235</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-7.png" class="kg-image" /></figure><p><a href="https://hamatti.github.io/nhl-235/">235</a> is my probably most used piece of software. I personally use it daily and it has almost 2k downloads at Crates. I originally built it to <a href="https://hamatti.org/p/00564284-791a-4767-a512-6f9797f6f2a8/hamatti.org/rust">learn Rust</a> but it has become a daily tool for me to stay up to date with NHL.</p><p>It hasn't received a lot of updates as I consider it mostly design-complete. Recently I did open up <a href="https://github.com/Hamatti/nhl-235/issues/43">an issue to allow display of configured favorite players when they provide assists to goals</a>. Given how the printout is currently implemented, it's a bit more than a few lines so it's been sitting in the issue tracker for a while but I'll get there one day.</p><p>I'm also looking into providing Linux and Windows compatible binaries now that I have a machine I can build and test them on and I'd like to have it as a Homebrew software as well so one wouldn't need to either install Rust/Cargo or download binaries from the web.</p><h2 id="upcoming-pokemon-tcg-deck-builder">Upcoming: Pokemon TCG Deck Builder</h2><p>I recently also started a bigger side project. I'm building an online deck builder for Pokemon TCG players. I've been designing it on a top level for the past half a year, thinking about different interactions and trying to figure out ways to make it the best experience for players and content creators.</p><p>One of my key focuses is to focus on Pokemon evolution lines instead of individual cards. All the current tools have you search individual cards and add them one by one but that's not how I've ever seen a player actually approach their deck building or describe their deck.</p><p>Instead, players describe their decks by the main Pokemon evolution lines. They might have a "4-4-4 Inteleon line" or a "2-2 Giratina VStar line". So why shouldn't our tools reflect on that? I'm building my tool around that idea. I've been experimenting with the tech and the UX a bit and I'm now in a place where the player can say "I want a 4-4 Raichu line" and get a selection of Pikachu and Raichu cards from their selected filters (for example, Standard or GLC format). Once they select which of each card they want, 4 copies of each will be added to the deck.</p><p>I hope this will streamline a lot of the annoying parts of building a deck.</p><p>In addition to that core idea, I'm playing around with a few other things to help content creators to share their decks on their videos and live streams in the best possible way.</p><p>I don't have any timeline for this project as it's something I work on whenever I have energy after work and other responsibilities. I did make very good early progress during the Easter holiday so I know most of the core pieces work and I'm in a situation where I have a lot of loose ends that I can tie up and polish whenever I have a bit of time and energy.</p>
Syntax Error #3: Who let the ducks out?
2023-04-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-3-who-let-the-ducks-out/
Syntax Error is a newsletter about debugging for developers, students, hobbyists, curious and duck fans.
In this month's third issue I write about the best friend of debuggers: rubber ducks.
Read full article at <a href="https://www.syntaxerror.tech/syntax-error-3-who-let-the-ducks-out/">syntaxerror.tech/syntax-error-3-who-let-the-ducks-out/</a> and either subscribe to the email or RSS feed to catch all of them.
How to sort a Pokemon deck list?
2023-04-12T00:00:00Z
https://hamatti.org/posts/how-to-sort-a-pokemon-deck-list/
<p>As a developer, sorting an array or list boils down to two things: a sorting algorithm and a sorting criteria. For day-to-day sorting, you don't usually need to worry about the algorithm part – you just use the built-in tools that come with the language unless you specifically need something different.</p><p>Over the Easter holiday (a luxurious 6-day holiday for me thanks to the 4-day work week) I started writing code for a new project of mine that I've been designing and experimenting ideas with for a while now. I'm building an online deck builder for Pokemon TCG community. I want to build a world-class experience of building decks for everyone: digital players who don't want to struggle with existing digital tools, content creators who want to showcase their decks in their videos and streams, competitive players who want to quickly iterate their decks and print proxies for quick play testing. More of that project later though, it's only bare bones so far.</p><h2 id="the-elements-of-a-pokemon-deck">The elements of a Pokemon deck</h2><p>A Pokemon deck is (usually) 60 cards consisting of different types of cards used to play the game. You have your Pokemon cards that are the main stars, your Trainer cards that help you navigate the game and fulfill your strategy and Energy cards that power up your Pokemon to do their fighting.</p><p>These get further subdivided: Energies come in two flavors, Basic and Special. Trainers have even more subcategories: Items, Supporters, Tools, Stadiums, Trainers, Goldenrod Game Corner Cards and so on. Pokemon come in different <em>stages </em>and <em>variants</em>: Baby, Basic, Stage 1, Stage 2, BREAK, ex, EX, GX, V, VMAX, VStar and the list goes on.</p><p>In addition to these categories, there are other properties that could be used for sorting: cards come in <em>sets</em>, each card (excluding basic energies) have a <em>number within the set</em>, there's also Pokedex numbering that isn't really a TCG property but ties together the Pokemon in-universe in a way that the other properties might not always do.</p><p>Pokemon cards also <em>evolve</em> from each other. A Charmander becomes a Charmeleon that becomes a Charizard – and this happens across the sets so that relation transcendes all the other properties.</p><p>There's (at least) one more aspect but let's start with these and I'll reveal it in at the end.</p><h2 id="what-are-players-expecting">What are players expecting?</h2><p>I'm in a good position because I don't have to invent something novel out of the blue. People have been building decks and sharing decks for decades so there's a lot of precedence. </p><h3 id="supertypes-pokemon-trainer-energy-">Supertypes (Pokemon/Trainer/Energy)</h3><p>One thing that I've found almost universally common is that the main order is: Pokemon, then Trainers, then Energies. The only places where I've seen this order changed is when people lay out their decks in real life for a picture to share. And even then, it's often Pokemon, then Trainers, and finally Energies go wherever there's space on the mat. Which I'll consider to be the same as "last". I don't think a deck has ever been truly identified by its energy.</p><p><a href="https://assets.pokemon.com//assets/cms2/pdf/play-pokemon/rules/play-pokemon-deck-list-a4-svi.pdf">The official deck list form</a> also uses this order which is probably one reason it's so prevalent.</p><p>Whew, we got one criteria down. I'll assign sorting weights to these <em>supertypes</em>: <code>const supertypeSort = { Pokemon: 1, Trainer: 2, Energy: 3 }</code> and compare those in my custom sort function. Smaller the weight, the earlier it comes in the list.</p><h3 id="subtypes-of-energies">Subtypes of Energies</h3><p>When dividing the cards within their <em>supertype</em>, I consider energies to be the easiest: there's usually only a handful of energy types in a given deck and there doesn't seem to be any consistency between different mediums on how to sort them so I'm sorting them basics first, special then. <code>const energySort = { Basic: 1, Special: 2 }</code>. After that, we can sort the specials and basics alphabetically by their name within their respective groups.</p><p>I'm making an assumption here that the order of energies doesn't matter much to the players. Luckily, it's an assumption that can easily be reverted by changing the numbers around if feedback shows that players wish to have their special energies first.</p><h3 id="subtypes-of-trainers">Subtypes of Trainers</h3><p>Next up, we can look at Trainers as the next easiest one. Let's see how different tools do it currently.</p><p>Pokemon has two official digital clients. Pokemon TCG Online – or PTCGO amongst friends – that is being sunset as we speak and Pokemon TCG Live – or Live amongst friends – that is taking the reign as the main platform to play Pokemon digitally.</p><h3 id="pokemon-tcg-online">Pokemon TCG Online</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/Screenshot-2023-04-11-at-14.56.49.png" class="kg-image" /></figure><p>Online starts with Items, follows it up with Stadiums, then Supporters and finally Tools.</p><h3 id="pokemon-tcg-live">Pokemon TCG Live</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image-2.png" class="kg-image" /></figure><p>Live sorts Trainers as follows: Items, Stadiums, Supporters, Tools, being the same order as with Online which is not a big surprise given both of these are Pokemon's own tools.</p><h3 id="limitlesstcg-com">LimitlessTCG.com</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/04/image.png" class="kg-image" /></figure><p>A popular Pokemon TCG resource site <a href="https://limitlesstcg.com/tools/imggen">LimitlessTCG.com has an image generator tool</a> that takes in a decklist and creates a shareable PNG image from it. It sorts Supporters first, then Items, Tools and finally Stadiums. That is very different from what Pokemon's tools have decided to do.</p><h3 id="physical-deck-images-from-the-community">Physical deck images from the community</h3><p>As I looked through pictures of decklists shared by the community in different Pokemon TCG Discord servers and social media, I noticed that there's no strong "one order fits all" unwritten rule here. Some people like to start with their supporters, others leave those to the end.</p><p>For now, I'm gonna go with the Items (and Item-like cards that follow similar rules of "use as many times as you want"), Supporters, Tools and Stadiums for now.</p><pre><code class="language-js">const trainerSort = {
Item: 1,
Supporter: 2,
"Pokemon Tool": 3,
Stadium: 4
}</code></pre><h2 id="the-hardest-one-pokemon">The hardest one: Pokemon</h2><p>We still have one main category left: how do we sort Pokemon. The thing with trainers and energies is that they are pretty much on the same level with each other. A Professor's Research might not be much more important to a deck than a Boss's Orders for example.</p><p>That's not the case with Pokemon. There are a few really interesting aspects of the Pokemon cards that make this sorting a bit more challenging.</p><h3 id="evolution-lines">Evolution lines</h3><p>Firstly, you want to keep evolution lines together. If you play Charmander, Charmeleon and Charizard, it's quite crucial to have those next to each other to improve the <em>readability</em> of the deck. But you can't look at two cards in isolation and say whether one of them is part of an evolution line in the deck or not. <em>(You can even play evolution cards even without all of their pre-evolutions in the deck.)</em></p><p>A card knows whether it evolves from/to and which card it evolves from/to so the knowledge is there. I just basically need to figure out a way to get that "outside" information into the sorting. One way to do this is to group the cards into subcategories based on their <em>evolution lines</em>. At that point, we wouldn't be sorting individual cards but evolution lines. Instead of deciding if Charmeleon should go before Noivern, we consider if the Charmeleon line (including potentially Charmander and Charizard) should go before Noivern line (including potentially Noibat).</p><h3 id="mains-supports">Mains & supports</h3><p>Second, there's a mental categorization of Pokemon cards into "main attackers" and "support" Pokemon that is deck specific and cannot be automatically deduced from the cards. One deck's supporter Pokemon may be another deck's main card. </p><p>This is something that I belive cannot be determined purely based on the data of the cards.</p><p>I haven't yet implemented this into the application but my plan is to allow the player to reorder the Pokemon in a way that stays with the deck through automatic sorting. That would allow the player to easily keep an eye on their important main lines when building a deck but also communicate their deck to people when sharing the deck to their community.</p><p>Other than that, the sorting pretty much goes the Pokemon types (Grass, Water, Fire, etc) in alphabetical order and then in alphabetical order based on the Pokemon's name within their type.</p>
Potluck #3: My workflow
2023-04-05T00:00:00Z
https://hamatti.org/posts/potluck-my-workflow/
<p><em>This is the final part in a three part blog series exploring my most recent tabletop design project, Potluck. Previously, </em><strong><em><a href="https://hamatti.org/posts/potluck-research-and-design">Research and Design</a> </em></strong><em>explored the origins and design considerations of the project and <strong><a href="https://hamatti.org/posts/potluck-the-deck">The Deck</a> </strong>looked at the form and function of the deck.</em></p><p><em>This part is for all of those who are interested in building similar projects themselves. I used technology and tools familiar for software developers to turn design and data into generating the project and keeping track of the project.</em></p><h2 id="basic-workflow">Basic workflow</h2><p>When I created the previous project, <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108">Project 108</a>, I built everything manually in a graphic designer. This meant that if I ever needed to make changes to layout, I needed to make it 108 times. Never again.</p><p>For this project, I wanted two things to happen: I wanted a way to manage the content of the cards in a convenient way and I wanted to be able to generate the full deck with a click of a button.</p><h2 id="data-csv-spreadsheets">Data: CSV & Spreadsheets</h2><p>I wanted to manage the data in spreadsheets because they map nicely to cards: row equals one card and column equals one element on a card. Also, the capabilities of spreadsheets make managing data easy: I could use formulas and autocomplete features to make it easier to manage the entire data set.</p><p>For text, I wrote the text into the cells and for images, I wrote the file's basename into the cells. I then stored the images in an assets folder in my project.</p><p>One big benefit of spreadsheets is that it maps nicely with CSV files and that's great because CSV files can be read by other software and also, they are a fully text-based format which makes it easy to track them in version control.</p><p>Here's a simplified example:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>number</th>
<th>suit</th>
<th>role</th>
<th>top-right-color</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>hearts</td>
<td>Guard</td>
<td>red</td>
</tr>
<tr>
<td>2</td>
<td>hearts</td>
<td>Priest</td>
<td>blue</td>
</tr>
<tr>
<td>3</td>
<td>hearts</td>
<td>Bishop</td>
<td>green</td>
</tr>
<tr>
<td>4</td>
<td>hearts</td>
<td>Handmaid</td>
<td>yellow</td>
</tr>
<tr>
<td>5</td>
<td>hearts</td>
<td>Prince</td>
<td>orange</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>To add a new element, I'd create a new column, give it a name and then fill in the data. For me, an "element" usually refered to a "slot in the card layout" but ended up sometimes having 2-3 columns per layout spot since sometimes I needed to change the layout a tiny bit for some items depending on other items.</p><p>Using spreadsheets fulfilled my first need: a way to manage the contents in a nice way.</p><h2 id="layout-xml-and-multideck">Layout: XML and Multideck</h2><p>Sometime in 2022, I learned about <a href="https://semicolon.com/multideck/multideck.html">Multideck</a>. It's a macOS software built for exactly this use case: combining CSV and layout files to generate cards.</p><p>One thing I really really really like about it is that it uses GUI to create the layouts and then <strong>stores them in XML files. </strong>XML files are text so they too can be nicely tracked with version control.</p><p>Multideck is an okay software. It does what it promises and can solve many problems. But as someone who is used to programming and tinkers in graphic design software daily, I keep dreaming of features from those two workflows. I wish there were more support for conditionals (I solved this by creating more columns in spreadsheets and using formulas there) and more tools to help with pixel-perfect layout (like rulers and snapping to those).</p><p>At one point my CSV file grew too large and Multideck kept crashing on startup so it doesn't support very long lines in CSV files. It's not the end of the world though because you can minimize the CSV quite a lot if needed with smart design and tooling.</p><h2 id="version-control-git-and-github">Version Control: git and GitHub</h2><p>I've mentioned twice above how happy I am of the text-based formats with CSV and XML. The reason for that is that it enables me to do iterative development with software development practices. I use <a href="https://git-scm.com/">git</a> as my version control tool in this project.</p><h3 id="quick-primer-into-git-for-non-developers">Quick primer into git for non-developers</h3><p>Git, or other version control software, works by saving steps in the process by comparing the changes – called "diffs". If you've ever worked on a Word project and have multiple versions named "Thesis_final.doc", "Thesis_final_2.doc", "Thesis_final_final.doc" and so on, you're familiar with the problem.</p><p>Git stores these changes in commits and each commit is a set of changes in text. If I add a new column into my CSV file, I can store that change as a commit. If I change something in the layout, I can commit that change in XML as a commit.</p><p>The beauty of version control is that I can jump back in time whenever I want into any other commit. So if on day 2, I experiment with something, then later decide to remove it and even more down the line, want to bring it back, I don't have to remember how it was done. I can look into the version history.</p><p>It also enables working in "branches" which means I can keep my main project intact and make experimental changes in a separate version without having to worry about making a mess.</p><h3 id="pull-requests-for-features-kinda">Pull requests for features... kinda</h3><p>In the beginning, I tried to work in a way where one pull request (a set of changes, can be multiple commits) would map to one feature in the deck. I wanted to have a history where I could see clear steps of each new feature or change.</p><p>To be honest, at some point, I got bit lazy with that as I got more excited about experimenting with new features. But the commit history is there and it's mostly feature-by-feature.</p><p>I kept the project in GitHub and kept updating the readme when new major changes happened. I did it so I had a reason to export individual items after each step so I would have a nice history of development of the project.</p><h2 id="the-big-picture">The big picture</h2><p>With these three main tools, I had a superpower in my hands: at a click of a button, I could generate a full deck that could be printed for testing or sent to a printing house for production. It made me more excited to experiment and to try out new things without having to worry about having to do everything 106 times over.</p><p>I think this was also a major reason for why this project is so much better than the previous one. I did kind of get "burnt out" by the previous one because every change required so much effort that I didn't want to add new things or make changes. Now, every change was easy to apply to all cards, easy to revert and because of the version control, I knew the good experiments would be saved and bad experiments wouldn't cause issues.</p>
Organizing a mess with cherry-picking
2023-03-29T00:00:00Z
https://hamatti.org/posts/organizing-a-mess-with-cherry-picking/
<p>Yesterday was the first time I used <a href="https://git-scm.com/docs/git-cherry-pick">git's <code>cherry-pick</code></a> functionality. I've known of it but had never had really good use cases. Until yesterday.</p><p>Working on a new product and codebase is always challenging at the beginning. There's so much unknown unknowns and complexity behind simple-sounding requests. I dove a bit too deep into a rabbit hole in my current work before I realized I was there.</p><h2 id="story-time">Story time</h2><p>I picked up a ticket that sounded relatively simple: add a field to one of the models (and build the plumbing around it to make it functional). I started working on that, made it functional and then took on to build the next piece: there was a comment in the ticket saying that as part of this work, another closely related piece should be implemented.</p><p>To implement that, I needed to do a bit of fundamental refactoring of other models and bit by bit, I ended up in a situation that I had a massive pull request that wasn't quite finished and had bits and pieces of all of these things.</p><p>It was exactly what the books about pull requests warned against: it did too many things, it had too many only vaguely related changes and it was huge. It was over 1k LOC net change across nearly a 100 files. To be fair, quite a bit of those changes were in tests and test fixtures but still it was bit too much. </p><h2 id="complex-mess-slows-me-down">Complex mess slows me down</h2><p>I had a really slow start to this week due to the grown complexity. I felt like I had gotten so deep to the hole that it was hard to navigate and maintain a sense of direction. I felt like I was running around in circles and by lunch time Tuesday, the negative feelings had overtaken my productivity and I wasn't getting anywhere.</p><p>After I had finished the process, I realized how much this problem was pulling down my productivity, my mood and me wanting to be a developer.</p><p>I decided to split the pull request into three pieces. Only thing was, I didn't know how. That's when I decided to finally learn how to use <code>git cherry-pick</code></p><h2 id="pick-em-cherries">Pick 'em cherries</h2><p>I started my splitting process by starting a new fresh branch from dev and cherry picked a couple of commits from the old branch that implemented the model refactoring bit. With <code>git cherry-pick [commit-hash]</code>, you can pick commits from other branches by providing the command the hashes you needed. Since I had an open pull request draft, it was easy to find and pick things from it.</p><p>This also provided me a great opportunity to clean up the commits a bit and combine commits with <code>git rebase -i HEAD~N</code> where <code>N</code> is the amount of commits I want to rebase and squashing commits into one and improving their commit messages. I sometimes notice I had forgotten a console.log, a commented out part or a <code>.only()</code> in tests after pushing the branch to remote which leads to occasional <code>"Oops, didn't mean to do that"</code> type of commits. I want to become better at cleaning those up as I go but since I'm not there yet, this fresh start offered a good opportunity for that.</p><p>Here's an example of the commit log at the time (newest first):</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">da39a3 Add more tests for A
4b0d32 Add validation for A
55bfef Refactor frontend to work with C
38749b Forgot a test runner comment for A
956018 Refactor model for feature C
ee5e6b Pure refactor of B
90afd8 Add field to form for feature A
070fg9 Create routes for feature A</code></pre><!--kg-card-end: html--><p>I added A/B/C markers to them to make these imaginary examples easier to follow.</p><p>I decided to start with <code>B</code> as it was a pure refactor with no effect on functionality:</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">git checkout -b new-branch-for-B
git cherry-pick ee5e6b</code></pre><!--kg-card-end: html--><p>A new branch with just that one commit picked to its own branch, ready to be reviewed! The new PR is so much nicer to review and can be merged in before my other ones as it's a pure refactoring of internals and doesn't introduce any new functionality.</p><p>I'd then continue with A and C respectively:</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">git checkout -b new-branch-for-A
git cherry-pick 070fg9^..90afd8
git cherry-pick 38749b
git cherry-pick da39a3^..4b0d32
git checkout -b new-branch-for-C
git cherry-pick 956018 55bfef</code></pre><!--kg-card-end: html--><p>You can cherry pick multiple commits by providing it multiple commit hashes or a range of commits with <code>..</code>. The <code>^</code> at the end of a commit means "include this commit".</p><p>Now, instead of one monster PR that was hard to mentally manage, I had three smaller ones and if I needed to change something on one of them, I could switch to that branch and make changes.</p><h2 id="oh-the-mental-clarity">Oh the mental clarity</h2><p>The benefits were so good. Not only did I create more manageable pull requests for the team to review, I also got myself out of the rabbit hole and back into much more manageable situation so I can finish the work for this feature. And I learned a new tool/technique along the way so yay me!</p>
A quick life update
2023-03-22T00:00:00Z
https://hamatti.org/posts/a-quick-life-update/
<p>It's been 9 weeks since I last shared a life update in the blog when I <a href="https://hamatti.org/posts/im-leaving-mozilla/">shared my decision to leave Mozilla and Berlin and move back home</a>. Quite a lot has happened since.</p><h2 id="moved-back-home">Moved back home</h2><p>After that last update, I wrapped up work, packed my bags and took the train and boat to home. I have to say, the boat ride from Travemünde to Helsinki was one of the most relaxing ones in my life. I had just finished up all the stressful things in life, had no plans for the near future and could do nothing but sleep, eat and read books at the sea. I slept so good on that first night on the boat.</p><p>I was also lucky that a friend offered their sofa to me for February so I could start looking for an apartment as I didn't to sign a lease without seeing an apartment. As soon as I arrived to Turku, I found an apartment that had a showing the same night, went there, submitted an application and two days later heard I got an apartment. What a relief!</p><p>I'm so happy with the new place. It's a nice ~60 square meter apartment with a really optimal floor plan (small bedroom, large living room that fits my work area and a living room area spaciously, a nice kitchen and plenty of storage room. I have three windows and from all of them I can see the forest. During the night, there are no artificial light coming in which is such a bonus.</p><p>One of the key reasons I chose to move back home to Turku is the familiarity of everything. I know how everything works, I have plenty of friends and family in the area and as a stress reducer, that has been such a good thing compared to living in a different country with different culture and language.</p><h2 id="started-a-new-job">Started a new job</h2><p>I got lucky with the apartment but even more with the job hunt. I have to say, I was so nervous to resign with no plans for the future. I <a href="https://hamatti.org/hire/">created a website</a> as a kind of job application in case someone heard about me looking for a job and wanted to learn more.</p><p>I had no idea that it would get nearly 70 000 views in the first 2 weeks. I ended up not sending a single job application as all my time went to answering the messages I got as 41 companies reached out. I ended up having deeper discussions with 15 and finally asked for 3 offers to choose from before choosing to join <a href="https://madbooster.com/">MadBooster</a>. We have a great small team of developers there and I've already learned so much from them and I'm excited for the support and onboarding I've received.</p><p>It was such an privileged position to be in and quite frankly one I didn't expect. But it was cool to see so many people in those companies either having been my students at the university or different workshops, readers of my blog posts and members of our Turku <3 Frontend community.</p><p>Being in that positioned really helped me because I knew I could turn down anything that didn't exactly fit my main criteria: no (people) leadership responsibilities, no client responsibilities and no (tech) lead roles + I wanted to do a 4-day week. It also gave me the ability to have very honest discussions with people as I didn't have to prove myself to anyone or convince anyone of anything.</p><p>It was lovely to see so many companies being open to me joining them with my terms during my struggles. I'm deeply thankful to all my friends and anyone in my networks who helped me during that job hunt to find a place where I can work while I start my process of getting myself healthy and back into good fit again.</p><p>After the first few weeks at the new job, I'm very happy with my choice. And the 4-day week has been such a brilliant decisions. I'm taking Wednesdays off on default so I never have to get through more than 2 days at once. Not only does it offer me a safe haven in the middle of the week to take care of stuff (like laundry, going for walks, shopping, cleaning, writing, etc) but it also provides me mental comfort upfront when I know there's no 5-day week waiting for me if I feel tired.</p><h2 id="leaving-devrel-temporarily-is-a-bit-scary">Leaving DevRel temporarily is a bit scary</h2><p>One thing I haven't talked about a lot with anyone is that not only did I change jobs and where I live but I also put my career on-hold for indeterminate time. I left DevRel, community building and all of that behind but I'm hopeful that in a few years, as I get better, I'd be able to make a comeback.</p><p>I worked so hard on making it in DevRel and after years of pushing myself hard, feeling insecure and impostor a ton and finally "making it", it lasted a few months and I had to take a break. It's definitely not what I imagined would happen when I joined Mozilla, quite the opposite.</p><p>Being away from all of that is probably not doing me many favors in the career thing but that's a trade-off I have to accept if I want to fix my problems. I do have one lifeboat project, <a href="https://hamatti.org/posts/a-quick-life-update/syntaxerror.tech/">Syntax Error</a> newsletter, that I hope keeps me afloat in that world just in case I can make it back there one day.</p><p>I am a bit relieved though. I often find myself laying in the bed thinking "I'm so happy I don't have any of those responsibilities right now". My daily stress levels are down like 1000 points (on an arbitrary imaginary stress scale).</p><h2 id="so-far-so-good">So far so good</h2><p>It's still very early to say many things but I'm really happy with how things have gone and how I feel right now. I've managed to say no to a few interesting things as I'm trying to learn not to fill my evenings and weekends with new stressors and while it's hard to say no to fun things, I'm proud of myself so far.</p><p>It's also quite nice to be home at 18 with the day's work already done and finished instead of leaving the office around midnight on Wednesday after the third event of the week, exhausted and worn out.</p><p>With the previous community and event work, there was almost never (other than summer and winter holidays) a moment when I'd be finished with one thing before starting a new thing. There was always a dozen of overlapping things going on. </p><p>On the contrast now, I really enjoyed when the other Friday I finished my tasks before the end of the day, spent the last few moments at work learning new things and then came to the office on Monday and was able to just tell my colleague that I had nothing on my table and he provided me with a new ticket. It was so refreshing and calming and I'm thinking I like this new thing.</p>
Syntax Error #2: print it like a boss
2023-03-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-2-print-it-like-a-boss/
Syntax Error is a newsletter about debugging for developers, students, hobbyists, curious and duck fans.
Second issue discusses the benefits of using printing as your debug tool, explores a few Elm related debugging methods and shares an interesting debugging story from Jesse.
Read full article at <a href="https://www.syntaxerror.tech/syntax-error-2-print-it-like-a-boss/">syntaxerror.tech/syntax-error-2-print-it-like-a-boss/</a> and either subscribe to the email or RSS feed to catch all of them.
A bunch of small game reviews: Village Rails, Skulls of Sedlec, Barotrauma
2023-03-15T00:00:00Z
https://hamatti.org/posts/bunch-of-small-game-reviews/
<p>
I want to try something new and write a review of something. What started as
one review, grew to three over the weeks when I couldn't quite make up my mind
about the style and tone of my reviews. You're welcome!
</p>
<h2 id="village-rails">Village Rails</h2>
<p>
I recently ordered
<a href="https://boardgamegeek.com/boardgame/358085/village-rails">Village Rails</a>, and once it arrived, I took it to my local game night for a spin. Spoiler
alert: we all enjoyed it a lot!
</p>
<p>
Village Rails is a 2-4 player game in which players build 7 routes of rails in
a 4x3 grid on their own area. To maximize scoring, they buy trips (scoring
cards) to their routes and combine tracks to their best ability to score on
different features.
</p>
<p>
Our first game was played with 4 players, all new to the game. I had watched a
few gameplay videos on Youtube so I was a bit familiar with the rules and
mechanics.
</p>
<p>
This is my review of the game and if you're looking to learn how the mechanics
work and the game plays, I recommend
<a href="https://www.youtube.com/watch?v=qncVl0mNKQs">Before You Play's playthrough video</a>.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/03/village-rails-example.jpg" class="kg-image" alt="A game play of Village Rails with a partially filled grid of rails, few copper coins and a market of cards next to the play area" />
<figcaption>An example setup by me (not from a real game play)</figcaption>
</figure>
<h3 id="great-mechanics-in-a-beautiful-theme">
Great mechanics in a beautiful theme
</h3>
<p>
Have I ever mentioned that I love trains? Whether it's traveling in real world
or dealing with them in games, a day spent with trains is a happy day.
</p>
<p>
One of my favorite things with the game is how everyone will always be able to
finish their 4x3 grid. Often in these kind of "fill a grid" games (for example
in
<a href="https://boardgamegeek.com/boardgame/204583/kingdomino">Kingdomino</a>) it's possible to take risks that lead to incomplete grids but in this game
you don't have to worry about that. The worst thing that is gonna happen is
that you score 0 points and gain minimum of 3 coins per finished route.
</p>
<p>
I really like it because it gives me one less thing to worry about and I can
focus on the routes and trips.
</p>
<p>
Each turn you pick a track card from a market of 7 available. The card
furthest down the line is free and for everything else, you need to put 1 coin
on top of each card that comes before the one you want. This is another
mechanic I really enjoy as it balances the game a lot: every card is free but
you pay to have a choice.
</p>
<p>
Paying for more choice also means you give money to other players, giving them
more choice down the line. You can find similar market mechanics in games like
<a href="https://boardgamegeek.com/boardgame/40692/small-world">Small World</a>
and <a href="https://boardgamegeek.com/boardgame/290236/canvas">Canvas</a>.
</p>
<p>
Scoring cards always cost 3 coins + one for each choice made similar to track
cards. As you start with only 3 coins, the beginning feels a bit crammed and
if your early Terminus cards (that provide money once a track is finished)
don't match with the free options you are provided, the game can become a bit
clunky at the start.
</p>
<h3 id="big-fun-in-a-small-box">Big fun in a small box</h3>
<p>
The game comes in a small-ish box and uses mini-sized cards. After sleeving
all the cards and replacing the cardboard coin tokens with metal coins, the
game just barely fits into the box (without taking away the pre-built
inserts). I'm planning to clear out the box and design & 3D print a custom
insert to make sure the game fits nicely and everything stays in place when
the game is in the backpack.
</p>
<p>
We had a really tight first game: the winner won with 107 points, I came
second with 106 and two others had 103 and 88 points. The points are public
knowledge during the game but they are tracked with dials that have tiny
numbers so nobody ever tried to even check what others had. It was exciting
until the very last moment of counting the points.
</p>
<p>
I'm looking forward to playing the game more in the upcoming weeks. But I give
the game already my recommendation if you're looking for a good gaming
experience.
</p>
<h2 id="skulls-of-sedlec">Skulls of Sedlec</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/03/image-1.png" class="kg-image" alt="A pyramid of colorful cards with pictures of skulls wearing different kind of apparel like crowns, crosses, roses, daggers and hoods." />
<figcaption>
A game of Skulls of Sedlec! (<a href="https://boardgamegeek.com/image/5275123/skulls-sedlec">from Eric/@kalchio in BGG</a>,
<a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">CC BY-NC-SA</a>)
</figcaption>
</figure>
<p>
<a href="https://buttonshygames.com/">Button Shy Games</a> is a very unique
and interesting board game publisher. They publish small games that fit into
(and come in) a wallet.
<a href="https://www.youtube.com/watch?v=LK1JySceRZk">Shut Up and Sit Down made a wonderful review video of 10 of their games</a>.
</p>
<p>
If you live in a country they ship to (not Finland unfortunately), you can
even become a Patreon for them and receive a new game
<strong>every month of the year</strong>. That's quite amazing. Luckily the
games are small and they sell
<a href="https://www.pnparcade.com/products/skulls-of-sedlec">Print and Play versions in PNPArcade</a>.
</p>
<p>
<a href="https://boardgamegeek.com/boardgame/303553/skulls-sedlec">Skulls of Sedlec</a>
was the first of their games I got to play, although I've been following them
for quite a while and have been interested in their other game
<a href="https://boardgamegeek.com/boardgame/251658/sprawlopolis">Sprawlpolis</a>
more but ended up going with SoS. I've been playing it quite a few times now:
it's great for short coffee breaks at work or when waiting for my mom's new
phone to sync backups.
</p>
<p>
The game is for 2 or 3 players (with expansions, you can also play single
player mode) and each player digs graves and places cards next to each other
in a pyramid on their own play area. At the end of a quick 18-card duel, you
count points: different roles score different amounts and it's all good and
fun.
</p>
<p>
It's a great short game for moments when you want to do something with your
hands but not think too much. Hence, a really good social coffee break game
with a colleague.
</p>
<h2 id="barotrauma">Barotrauma</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2023/03/image-2.png" class="kg-image" alt="A mostly dark screen with a character in the middle shining blue light towards a mysterious orb inside a metallic structure" />
<figcaption>
from <a href="https://barotraumagame.com/">https://barotraumagame.com/</a>
</figcaption>
</figure>
<p>
<a href="https://store.steampowered.com/app/602960/Barotrauma/">Barotrauma</a>
is a PC/Mac game that's been in Steam in early access for a while but got its
1.0 release earlier this week. I got to spend a lovely evening with old
friends at a release party they threw yesterday but that's another story for
another day.
</p>
<p>
The game itself is a a 2D co-op space submarine simulator rpg. I'm not sure
what it is with Turku based game companies and deep sea/space but there seems
to be a correlation.
</p>
<p>
I got Barotrauma for my Steam Deck and it works great on it. Although, I do
recommend using a separate mouse and keyboard and screen for the game, it's
not exactly made to be a handheld. That's another thing I really like about
Steam Deck: it can play handheld on-the-go games as well as more traditional
PC games that benefit from large screen, mouse and keyboard.
</p>
<p>
First things you notice when you start playing is the fun ragdoll-style
character physics and bit quirky controls. I got used to those quite quickly
though and even though I'm still very early in the game, its setting, dialog
and story captivated me and I'm already waiting to get deeper into the world
of Barotrauma.
</p>
I combat impostor syndrome with building in public
2023-03-10T00:00:00Z
https://hamatti.org/posts/i-combat-impostor-syndrome-with-building-in-public/
<p>I have a massive impostor syndrome and I've had it for as long as I can remember, probably since the school started grading us in the 2nd or 3rd class. My professional life has been a constant battle against it and so far I've managed to push through despite it.</p><p>When I attended <a href="https://developerhood.com/">Elisa Heikura</a>'s fantastic workshop on impostor syndrome a couple of years back, I found myself in every category she listed as possible ways to feel impostor syndrome. Luckily, I'm not alone as this is such an epidemic in the software industry even though many don't speak about it a lot.</p><p>For most of my life, my only tool to combat it was stubborness. There was no way I was gonna let it stop me or ruin my life even though I've probably skipped quite a few things and opportunities over these years because of it.</p><p>More recently, I've found a new way that can sound a bit backwards and unintuitive: building and learning in public.</p><h2 id="learning-building-in-public">Learning/building in public</h2><p>I've shared <a href="https://hamatti.org/posts/learning-in-public/">my approach to learning in public before</a> in a few venues but it's mostly been focusing on the effectiveness from the perspective of learning new things, improving skills and meeting great people.</p><p>It can sound bit backwards to think that someone who's worried about their skills would like to share their work and outcomes openly to the world but hear me out.</p><p>For me, a big part of my impostor syndrome is that I feel it's so hard to guess/estimate/judge my own skills. I have no idea how good I am at something and I worry that if I say I'm good at something, someone believes it and then finds out down the line that I'm not.</p><p>I didn't choose to build in public because of this but it has turned out to be a great tool in my arsenal. For example, I've been solving Advent of Code puzzles and writing explanations to them for a few years, publishing them <a href="https://github.com/Hamatti/adventofcode-2022">in GitHub</a> and <a href="https://hamatti.org/adventofcode/2022/">on my website</a> and I've been openly working on <a href="https://hamatti.org/posts/learning-rust-pattern-matching/">my Rust learning project</a> on my blog and in meetup talks.</p><p>By building a body of work of different kind of projects and sharing them openly helps me pass the burden of responsibility to the reader. "Here's what I've built, what the code looks like and what I've learned along the line. You can decide if it's good enough."</p><p>It doesn't always work perfectly though. Because being active in sharing and talking about things does skew public perception quite a bit compared to those who don't. I've been offered Rust development jobs due to being rather active in the scene even though I wouldn't let myself write production code. This can bring its own impostor syndrome challenges too.</p><h2 id="it-gets-easier-as-the-portfolio-grows">It gets easier as the portfolio grows</h2><p>I've found that about now, I have quite enough of projects and code out there that I feel quite confident that I'm able to share a good and realistic view of my skills. If I want to showcase my documentation/technical writing skills, I primarily point towards the Advent of Code solutions. If I want to showcase my ability to learn something new, Rust project it is. And so on and so on. I have quite a few technical blog posts in this very blog that I can point to when it comes to specific individual things.</p><p>It still never takes it fully away.</p><p>I recently started a new job and while job hunting and discussing with a lot of companies, I was very worried about my skills and if I'm able to present myself in a realistic way to potential employers.</p><p>1.5 weeks into a new job, I'm happy now and have been doing quite well so far. But that's another story for another time.</p>
Potluck #2: The Deck
2023-03-01T00:00:00Z
https://hamatti.org/posts/potluck-the-deck/
<p><em>This is the second part in a three part blog series exploring my most recent tabletop design project, Potluck. The first part, </em><strong><em><a href="https://hamatti.org/posts/potluck-research-and-design">Research and Design</a> </em></strong><em>was published last month and the final part <strong>My workflow</strong>, will publish on the first Wednesday of April.</em></p><p><em>This part explores the deck and its form and function. In the last post I'll go into my workflow and what tools I used to build it.</em></p><h2 id="the-deck">The Deck</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/poster-landscape.png" class="kg-image" /></figure><h3 id="the-numbers">The Numbers</h3><p>The core of this deck is the main numbering. The deck consists of 106 cards, numbered from 0 to 104 with an added 00 (double zero). </p><p>The main number is big and bold in the center to make it easy to read and uses a font with a shadow to make numbers easier to recognize and to make 6s and 9s easily distinguishable from each other (which had proven to be a problem in my previous deck):</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/rotated-66s.png" class="kg-image" /></figure><p>On the left, you can see card 66 from my previous deck and next to it the same card rotated. On the right, you can see 66 with the new font. We ran into this problem multiple times when playing 6 Nimmt! with the old deck so I decided to choose a font that solved the issue.</p><p>Before I decided on the font change, I also experimented with having a graphical indicator:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/experimented-nine-identifer.png" class="kg-image" /></figure><p>I really liked that visual (thanks Marlo for the inspiration and idea for that) but decided to change the font in the end.</p><p>These numbers can be used for a lot of different games: 6 Nimmt, No thanks, The Game, Fugitive and many others, for example these <a href="https://boardgamegeek.com/geeklist/5498/games-sequentially-numbered-cards-aka-games-you-ca">56 games</a>.</p><p>These numbers are also replicated on the top left, so if you fan your cards out with the top left visible, you can see the number there too (as you can see in the image of 9 above.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2023/01/top-left.png" class="kg-image" /><figcaption>A few examples of top-left elements</figcaption></figure><p>The numbers in the top left corner have a few variants.</p><p>The colors run through black (0-10), red (11-20), blue (21-30), brown (31-40), green (41-50), yellow (51-60), purple (61-70), grey (71-80) and then again black (81-104). From 0 to 70, these represent the colors in <a href="https://boardgamegeek.com/boardgame/225482/seas-strife">Texas Showdown</a> (also known as Seas of Strife).</p><p>Some numbers also have a smaller number in parenthesis next to them. These show the highest value available in that color when playing Texas Showdown. They serve a small but important part in that game and is only needed on a closer inspection so the text can be smaller too.</p><h3 id="playing-cards-in-8-suits">Playing cards in 8 suits</h3><p>On the bottom left corner, rotated 180 degrees, are traditional French-suited playing cards – but with 8 instead of 4 suits. I added two blue (coins and stars) and two green (swords and triforce) suits.</p><p>These can play thousands of card games and while I very rarely play those games, I wanted to add them to the set for the sake of having them there if I need them.</p><p>In addition to traditional card games, these can also be used in a many "modern" games that require certain groupings. With 8 suits and 14 values in each, the deck can play games like <a href="https://boardgamegeek.com/boardgame/56692/parade">Parade</a> and <a href="https://boardgamegeek.com/boardgame/140934/arboretum/">Arboretum</a> (for 2-3 players).</p><h3 id="small-quality-of-gaming-additions">Small quality of gaming additions</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/quality-of-gaming.png" class="kg-image" /></figure><p>What makes this deck less universal (I explored this a bit in the <a href="https://hamatti.org/posts/potluck-research-and-design">previous blog post</a>) is that I made decisions based on the games I like. And to make those games easier to play, I added very specific things for them.</p><p>Texas Showdown's max value I mentioned above is one of those. The bottom left corner has bullheads for 6 Nimmt! scoring. Above the numbers 2 to 41 are footprints that help you remember how many sprints you get with them in the Fugitive. Some of the top right corner circles have handshake icons to make them more thematically suited for Lost Cities.</p><h3 id="roles">Roles</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/02/potluck-roles.png" class="kg-image" /></figure><p>Some games that I really enjoy are <a href="https://hamatti.org/posts/hidden-identity-in-table-top-games/">social deduction games with hidden identities</a>. They usually don't rely on much game mechanics but rather social skills, bluffing, manipulation and deduction. They are great "party" games because they are not complex with mechanics and don't rely on a lot of components or long setups.</p><p>Games like <a href="https://boardgamegeek.com/boardgame/129622/love-letter">Love Letter</a> and <a href="https://boardgamegeek.com/boardgame/131357/coup">Coup</a> are played with cards that have roles and these roles change constantly throughout the game. Similar but in a very fun way distinct is <a href="https://boardgamegeek.com/boardgame/139030/mascarade">Mascarade</a> where you try to deduce what role you might have at any given time as the cards shuffle around the board. <a href="https://boardgamegeek.com/boardgame/925/werewolf">Werewolf</a> (often also known as Mafia) distributes players special roles and then plays completely through discussion and voting with no other mechanics through coins, tokens nor cards.</p><p>The same text section of the cards is also used for money cards to be used to play the wonderful <a href="https://boardgamegeek.com/boardgame/172/sale">For Sale</a>.</p><h3 id="skulls-roses-and-moving-animals">Skulls, roses and moving animals</h3><p>On the top center, I made a bold experiment and added rather large elements. This is the one thing that I'm most concerned of prior to more play testing. </p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/02/potluck-top-center.png" class="kg-image" /></figure><p><a href="https://boardgamegeek.com/boardgame/92415/skull">Skull & Roses</a> is a wonderful bluffing party game that I wanted to include in the set as it's a great short game that requires very little rule explanation and can be played in a pub or while waiting for a train for 5 or 30 minutes.</p><p>I added 6 different sets of skulls and roses to accomodate 6-player games but also to give the players the option to choose the ones that best match their personality.</p><p>While I had reserved space for those in the top, I had a lot of cards with nothing there so I decided to add <a href="https://boardgamegeek.com/boardgame/160477/onitama">Onitama</a> moves. Add a 5x5 grid and a few meeples and you can play the great "chess-lite" style game about moving your pieces according to different animal patterns. </p><p>It does require carrying extra pieces and I haven't quite solved that puzzle yet. I'd love to have a custom designed playmat that would support multiple games but that's a way harder design puzzle than semi-universal cards. Alternatively, e-ink neoprene playmats would be amazing.</p><p>I'm bit concerned because when I'm looking at the cards in isolation (ie. in the design mode), they can feel quite crammed. From <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">my previous set</a>, I learned that it's actually quite easy to ignore the other bits when playing a specific game so I pushed the cards a bit more this time to truly test this hypothesis. </p><p>After all, every step on the way is experiment and progress. I've been telling that to myself when I've been doubting certain decisions. I'm creating a one-off project for myself but still somehow I hear the inner critic taking part in this way too much.</p><h2 id="my-other-table-top-projects">My other table top projects</h2><p>If you're still here, it's likely the topic interests you. You can continue learning about my other projects:</p><ul><li><a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">Minimal Travel Table Top Game Collection 1</a>. This is where it all started.</li><li><a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/">Minimal Travel Table Top Game Collection 2: Social Distancing Edition</a> was my answer to lockdown and having to play alone.</li><li><a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Minimal Travel Table Top Game Collection 3: Project 108</a> was my first attempt to create this kind of deck that Potluck is a direct spiritual successor of.</li><li><a href="https://hamatti.org/posts/my-pockets-are-full-of-games/">My pockets are full of games</a> is an exploration of all sorts of projects that I've worked on.</li><li><a href="https://github.com/Hamatti/taajuus">Taajuus</a> is an open-source digital version of a great board game Wavelength that I built for a team activity at work during the pandemic lockdown.</li></ul><p>April 5th I'll wrap up this project with the last blog post in series where I talk about the workflow and tools that I've used to build Potluck.</p><p>Let me know <a href="https://mastodon.world/@hamatti">in Mastodon</a> what are your favorite board games!</p>
Syntax Error #1: Welcome
2023-02-17T00:00:00Z
https://hamatti.org/posts/external-syntax-error-1-welcome/
Syntax Error is a newsletter about debugging for developers, students, hobbyists, curious and duck fans.
The first issue is now out! In this issue, I start our shared exploration of debugging techniques, look into Python's syntax error and share one my favorite debugging stories from my career.
Read full article at <a href="https://www.syntaxerror.tech/syntax-error-1-welcome/">syntaxerror.tech/syntax-error-1-welcome/</a> and either subscribe to the email or RSS feed to catch all of them.
The imperfect mess of note taking
2023-02-15T00:00:00Z
https://hamatti.org/posts/the-imperfect-mess-of-note-taking/
<p>I'm bit of a productivity junkie and a note taking addict.</p><p>For years, I've searched for the best way and tool to organize my thoughts, collect and store notes, plan upcoming things and manage my todo lists. I'd watch hours of Youtube videos and read tons of blog posts of people sharing their setups and I'd try out every app under the sky. Nothing seemed to click.</p><p>Only once I let go of my need to find a good solution and started to embrace the chaos, I found something that works. Let's take a look at my imperfect mess.</p><p>A big moment was when I stopped trying to make an ultimate one-stop-shop for all my notes, scribbles and thoughts.</p><p>Instead, currently I use:</p><ul><li><a href="https://www.notion.so/">Notion</a> as my long-term note storage and planning tool for continual content (blog calendar, a record of my talks & workshops, project boards etc). I absolutely love Notion.</li><li>OnePlus/Android built-in Memo tool. I can quickly swipe right from my home screen to reach my notes and I use it to quickly jot down ideas that I'm afraid I'll forget in the next 10 minutes. Blog post ideas, project ideas, board games I played on a game night, addresses, keycodes etc. Every now and then I go through and delete all the obsolete ones.</li><li><a href="https://www.goodnotes.com/">GoodNotes on iPad</a> is my go-to note-taking tool when I'm designing or planning something. I like hand-writing more than typing when trying to get thoughts from my head to a paper. I use it for anything that has a visual component as it's easy to draw with it but also for blog posts or other larger text pieces that need planning.</li><li>Paper notebooks, oh so many paper notebooks. I love writing by hand and having a notebook or two or three or ... is always handy. I have one in every bag and backpack that I own and smaller ones in every pouch and packing cube so that I'm never without a pen and paper when ideas come. I much prefer taking out a notebook and pen than a digital device if I'm sitting in a cafe or taking notes from a meeting with another person.</li><li>Post-its I have everywhere too. They are great for brainstorming and when thinking about things that have complex relationships between things.</li></ul><p>And I have zero processes or habits on transferring stuff between these. I stopped caring and it made my note taking way better.</p><p>I do of course do it a bit: my quick thoughts jotted down on Android often end up being moved to my Notion's blog planner once I decide that something's a good idea. On the other hand, I still have some blog post/project ideas from 2019 there because I haven't quite decided yet if I want to act on them.</p><p>Or if I plan a rough outline for a blog post on GoodNotes, I often transform that into a more coherent plan to my Notion. But sometimes I just write the blog post directly and never transfer the midway thoughts anywhere.</p><p>I even have overlapping notes about the same things in different places. I have one notebook in GoodNotes about a project that I'm planning to start and notes about the same project in a paper notebook and I don't feel any particular urge to combine them or move things around.</p><p>The process of writing things down is the powerful one. It's much more important than the ability to find those notes later. When I was in school, I would write extensive notes about everything I was studying but very rarely read them again when prepping for an exam because the process of writing them down had done the job of me remembering and understanding them.</p><h2 id="example-blog-calendar">Example: blog calendar</h2><p>I publish blog posts every Wednesday on this blog and I come up with a lot of ideas throughout the days. Some of those ideas are good and worth writing about, others don't make it to the final blog.</p><p>I jot down every idea <em>somewhere</em>. Most often, the first stop is my phone's Memo where I just write down the idea in one liner.</p><p>After that, I have two places in my Notion for them: there's an Idea Bank page which is my long-term storage for any idea that has survived the initial screening. Sometimes ideas are there for months or even years, I add bullet points to them and marinate the idea further.</p><p>Then, I have a "Post ideas" section on top of my Blog Calendar page in Notion which is kinda the same as Idea Bank but rather for ideas that I'm more likely to write in the upcoming month or two. If I find that something stares at me on that list for a long time, I move it to the Bank.</p><p>And final part part and the main part of my Blog Calendar is the calendar itself.</p><p>It's a Notion page that has a list of months and under each month, I list each week number in the year:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/Screenshot-2023-01-31-at-12.20.40.png" class="kg-image" /></figure><p>Once I've decided to write something (or already wrote something), I find a Wednesday for it. Sometimes I have recurring series (for example, this year's Feb-Apr, each first Wednesday of the month will be about my Potluck table top project) so I map them out long into the future. Some are recurring annual posts like my Year in Review that I put for the last Wednesday of the year when I start a new calendar. And others I put in and then often shift around if I want to balance the content (tech/community/personal) or if something more time critical comes up.</p><p>Having this calendar feature has been a crucial factor in me being able to keep up a (mostly) weekly publishing schedule. I also use color coding for posts based on if they are technical or not.</p><h2 id="todo-and-tada-lists">Todo and Tada lists</h2><p>Another subcategory of this is todo lists. How does one effectively organize the upcoming tasks in a way that are easy to check, to mark done or to add notes to.</p><p>I haven't found a satisfactory solution yet. So sometimes I don't have a todo list at all, sometimes I start the week by taking an empty page of either paper or GoodNotes and start writing things on it with checkboxes. It works fine enough but I'd like to discover a better solution.</p><p>If you speak Finnish or trust translate services, I recommend checking out <a href="https://www.rollemaa.fi/ticktick-tehtavalista-aikataulutettuna-viikkokalenteriin/">Roni's blog posts about various todo apps and his flow.</a></p><p>Todo lists have one major problem though. It's a bit depressing to codify your life into lists of tasks to tick off day after day, week after week, year after year.</p><p>Instead, I've been playing around with an idea of <strong>tada lists,</strong> where instead of an endless list of tasks to execute, I keep a list the best things I've done in a day, week or month. The things that truly mattered, things that made me happy and things that really excited me.</p>
In defense of Quote Toots
2023-02-08T00:00:00Z
https://hamatti.org/posts/in-defense-of-quote-toots/
<p>This blog post is three opinion pieces in a trenchcoat because they are all related and I couldn't come up with a solid post for each individually. I'm going to talk about 1) Quote Toots, 2) my feelings towards people wanting each app to be the same and 3) how bad it is when company owns the content and the delivery.</p><p>Let's start with the toots.</p><h2 id="quote-tweets-and-toots-are-great">Quote tweets and toots are great</h2><p>Since the start of the most recent Twitter exodus into Mastodon, one much debated topic has been whether Mastodon should implement a quote feature or not. </p><p>So what is a quote tweet/toot? It's a feature that got popular in Twitter where a user, in addition to retweeting someone's post, can add their own commentary on top. It looks like this:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/Screenshot-2023-01-30-at-20.25.00.jpg" class="kg-image" alt="Me quote tweeting Mike Conley's tweet. Mike's original tweet is an announcement on his upcoming livestream and my quote tweets says "Mike's streams are wonderful, highly recommend checking them out!"" /></figure><p>It's a feature that can be used to share great things to your followers with your personal recommendation but also you can use it to demonize, target & rally followers against someone. So like any technical feature, it has its pros and cons.</p><p><a href="https://scott.mn/2022/12/30/quote_toot_is_link/">Scott Feeney wrote about the technical side of quote toots</a>. Scott talks about how a quote toot is a link to another toot that is enhanced by a rich preview, interactability inside your instance, notifications to original tooter, an easy interface to quote toot and a way to see all the other quote toots from original toot. On another post, he also <a href="https://scott.mn/2022/10/29/twitter_features_mastodon_is_better_without/">said that Mastodon is better without quote toots</a>.</p><p>The developer of Mastodon has stated that he believes it <a href="https://mastodon.social/@Gargron/99662106175542726">inevitably adds toxicity to people's behaviours</a> and thus does not want to add it. This has been at the center of many arguments. <a href="https://privacy.thenexus.today/black-twitter-quoting-and-white-toxicity-on-mastodon/">Jon Pincus wrote about quote toots</a> and added a great amount of quotes from other people on the topic. <a href="https://absolutelymaybe.plos.org/2023/01/12/quote-tweeting-over-30-studies-dispel-some-myths/">Hilda Bastian talked about this myth of quote tweets being inevitably toxic</a>.</p><p>I personally wish to have quote toots. I often see things I'd like to share to my community but just boosting a post without adding my own commentary feels empty and doesn't really bring that much value with a lot of posts. Some have argued that I could reply to the original post and then boost my own post but it feels even more out of place since I'd be replying to another human but writing it to be aimed at my community. Others have argued that without quote toots, people are more likely to reply to people and that's a healthier interaction. I disagree with that because they are two messages aimed at completely different audiences.</p><p>I run two Mastodon accounts: my personal one at <a href="https://mastodon.world/@hamatti">@hamatti@hamatti.org</a> and one for our meetup at <a href="https://mastodo.fi/@turkufrontend">@turkufrontend@mastodo.fi</a>. Sometimes, I like to share other meetups' and communities' posts to our audience but I would really like to tell them why they should be interested in or why I'm sharing these posts.</p><p>I haven't looked at data but instinctively I feel like a personal note attached to a share is more likely to gather interest from one's own community than just a retweet/boost.</p><p>Each account (person or community or other entity) writes their posts with their own audience in mind and that audience is usually not the same as someone else's which means the words chosen and tone used wildly differ.</p><p><strong>I don't know if we'll ever see quote toots but I really hope we do. I love the positive effect it can have in showcasing other amazing people and communities and their work in the fediverse to new audiences.</strong></p><h2 id="why-does-everyone-want-each-app-to-do-all-the-same-things">Why does everyone want each app to do all the same things?</h2><p>I'm gonna take a step back, look at things on a higher level and slightly contradict my own wishes above. Life sure is complex sometimes.</p><p>Whether it's in the case of Mastodon's Quote Toot feature, a feature in video conferencing tool or in your favorite app for X, I really often see (and do it myself too) people wishing this one software had all the same features as this other software.</p><p>And I've started to wonder, why that's the case? Isn't it great that there are different options with different features so everyone could find the one they enjoy. Rather than having five different services from five different companies, with all the same features so all you can choose is who gets your patronage.</p><p>I'm not sure if it's always been like that but I only started notice it lately. Maybe there's some law of nature that all (commercial) creations gravitate towards the same optimized solution. And maybe art is the only thing that can truly set us free from this.</p><p>An yes, my desire to have quote toots in Mastodon is exactly about being quilty of this same thing myself. I've noticed the same thinking in myself with RSS readers and podcast software. I've also tried to actively move away from that thinking and to embrace the idea of difference and variety.</p><p>Spending time on this topic a lot lately has brought me to the next conclusion:</p><h2 id="a-problem-is-combined-ownership-of-content-and-delivery">A problem is combined ownership of content and delivery</h2><p>If you want to browse web, you have a decent selection of options: you can use <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>, Chrome, Edge, Safari, Brave, Vivaldi and a collection of other browsers. As long as a browser displays websites according to a standard, you can choose between other features that match what you enjoy.</p><p>But if you want to watch a video from Disney+, you must use an app or website built by Disney. If you wanna watch Netflix, same but with Netflix-built apps. And the same applies to all these services. So you can't select your software based on the features it offers but what content you want to see with it.</p><p>And the same is with (many of) your communication tools: Teams, Google Meet, Slack, Discord, and so on, all of them need a service and tool that are built by the same company. There are of course alternatives to these services (like using Matrix or IRC) but you can't just select a different app to use Slack.</p><p>This is a horrible thing for users everywhere. I dream of the world where companies would build their services on top of open standards and then allow anyone to build tools to use and interact with them.</p><p>This sprang to my mind when <a href="https://tapbots.com/ivory/">Ivory</a> came out and there was a bunch of discussion around that. Some felt it was too expensive (or not complete enough to pay yet) and there are differing opinions on what features to focus on. And I think it's beautiful that you can choose (or build or pay someone else to build) your software that you use Mastodon with. This separates the development of the platform (Mastodon) from development of the apps (website, Ivory, Tusky, Mast, Toot! <a href="https://joinmastodon.org/apps">and many others</a>). This definitely leads to innovation on both: platform needs to innovate to be a place people want to be in and the apps need to innovate if they want users.</p>
Potluck #1: Research and Design
2023-02-01T00:00:00Z
https://hamatti.org/posts/potluck-research-and-design/
<p><em>This is the first part in a three part blog series exploring my most recent tabletop design project, Potluck. The other parts, <strong>The Deck</strong> and <strong>My workflow</strong>, will publish on the first Wednesday of March and April respectively.</em></p><p><em>This part explores the background of the project and the research and design considerations that went into making it a reality. In the upcoming posts I'll go into the deck and its cards itself and what tools I used to build it.</em></p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/banner.png" class="kg-image" alt="Potluck - Minimal travel table top game collection 4" /></figure><h2 id="what-is-potluck">What is Potluck?</h2><p><strong>Potluck</strong> is a semi-universal deck that fits into a pocket and plays dozens of different games.</p><p>Combining travel and board games is not the easiest puzzle in the world. Board games often come in large retail boxes and even with smaller boxes, carrying multiple of them in a backpack is not really feasible.</p><p>Potluck was born out of that problem in my life. I'm always on the move and meeting people around the world and wanted something that I can always carry with me and quickly bring to the table to play a game or two.</p><p>It builds on top of the three previous Minimal Travel Table Top Game Collections. <a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">The first Minimal Travel Table Top Game Collection</a> one was my entry into this idea and combined a few existing games into double-sided cards. <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/">Minimal Travel Table Top Game Collection 2: Social Distancing Edition</a> was a child of the pandemic and combined Print'n'Play published solo games into a similar deck than the first one. <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Minimal Travel Table Top Game Collection 3: Project 108</a> was my first attempt at building a semi-universal deck where each card can play multiple games and not just one.</p><p>This one, <a href="https://hamatti.org/tabletop/potluck/">Minimal Travel Table Top Game Collection IV: Potluck</a> is a direct sequel on the third one. It takes the same idea and mostly same design and improves it a lot.</p><p>I have mostly completed the design process but since I'm in the middle of moving countries, I haven't ordered the physical cards yet. I'll show some real cards in a later blog post once I get them.</p><h2 id="the-universality-of-playing-cards">The universality of playing cards</h2><p>What does it mean for a deck of cards to be <strong>universal</strong>?</p><p>In my research, I found it's mostly used to describe a deck that implements one or more <strong>systems</strong>. The elements in a card don't match one to one with a specific game but rather builds a system that games can then use to implement different rules.</p><p>A basic example of universal system is the traditional playing cards - whether the <a href="https://en.wikipedia.org/wiki/French-suited_playing_cards">French</a> or <a href="https://en.wikipedia.org/wiki/Italian_playing_cards">Italian</a> (or probably many others, these are the one's I'm familiar with). By using the French-suited one as an example, with cards ranging from 2 to 10 with J, Q, K and A in four suits (hearts, spades, clubs and diamonds), you can play nearly unlimited amount of games. <a href="https://en.wikipedia.org/wiki/Klondike_(solitaire)">Solitaire</a>? Got it. <a href="https://en.wikipedia.org/wiki/Texas_hold_%27em">Texas hold'em poker</a>? Absolutely. <a href="https://en.wikipedia.org/wiki/Ristiseiska">Ristiseiska</a>? Yup. <a href="https://www.i-p-c-s.org/wp/games/">The International Playing Card Society IPCS estimates</a> there are roughly 1,000 to 10,000 games that can be played with that system.</p><p>Other people have taken this idea and added complexity in attempt to increase the amount and variety of games that can be played with one deck.</p><p>What originally got me interested in this hobby was <a href="https://boardgamegeek.com/boardgame/291951/everdeck">The Everdeck</a> that <a href="https://thewrongtools.wordpress.com/2019/10/10/the-everdeck/">documented their design process</a> very nicely. </p><blockquote><strong>The Everdeck is designed with a ruthless combinatorial efficiency. </strong>Beneath its minimalist pen-and-ink design lies layers of mathematical and linguistic patterns. This isn’t just a deck with haphazardly placed extra glyphs; rather, it aims to be both beautiful and practical. - <a href="https://thewrongtools.wordpress.com/2019/10/10/the-everdeck/">The Everdeck</a></blockquote><p>In similar way to The Everdeck, <a href="https://boardgamegeek.com/boardgame/251957/singularity-deck">Singularity Deck</a> is another system that is built around a more pronounced theme than the four-suited playing cards. Both of these systems combine universal, math- and probability-based systems with thematics and beautiful design to create an interesting and captivating playing experience.</p><p>There's also <a href="https://boardgamegeek.com/thread/1459848/article/23899182#23899182">minimal:deck</a>, <a href="https://boardgamegeek.com/boardgame/59655/rainbow-deck">Rainbow Deck</a>, <a href="https://boardgamegeek.com/geeklist/187686/swiss-army-deck">Swiss Army Deck</a> and many others. I spent quite a while researching what was already there and how other people had approached this.</p><h2 id="potluck-is-only-semi-universal">Potluck is only semi-universal</h2><p>I kinda wanted to build a universal deck. But I also wanted to make it easy for myself to play certain games without having to do the mental gymnastics every time. So I built what I consider to be semi-universal: it's not a purely mathematical concept and game system implemented with cards but a deck of cards that help me play my favorite games with my favorite people.</p><p>But it's also somewhat universal. I took great ideas from the other projects like adding extra suits (mine has 8 in 4 colors, essentially a double deck). But instead of making universality the driving force of my design, I used a list of games as that (and had to do a few painful omissions).</p><p>The main elements that make it more universal than not is the main numbering (00, 0-104) and the suits and values of traditional playing cards. But it also incorporates more direct elements that are specific to maybe only one or two games: scoring systems, the amounts of specific colors and values, roles and rules and even some movement cards.</p><h2 id="the-moral-and-legal-dilemma">The moral (and legal) dilemma</h2><p>One of the things that I have been thinking a ton ever since my first project has been around the morality and legality of these projects. In legal terms (I'm not a lawyer so don't know if this is actually true), you can't copyright or patent game mechanics or rules for games. You can copyright and trademark the implementation of the game, the graphics, the rulebook itself and so on.</p><p>As I'm not much of a graphic designer and I've been building these for my personal use, I've borrowed some images and hence, haven't been distributing any elements of my decks.</p><p>Potluck is the first version that uses only properly licensed (either made by me or using things like icons with a creative commons license) elements and thus, can be distributed.</p><p>Which brings me to the moral dilemma. Even though my project does not infringe any IP laws, a lot of creative work has gone into creating those games, playtesting them, marketing them and so on. And I love this hobby and I want to keep supporting the game designers and local game stores so that this hobby can keep on going.</p><p>The reason I've built my decks has never been to save money but rather to save space. As someone who travels a lot, I want to keep my games always with me and have the variety of options, so I've built these pocket-sized systems.</p><p>That's why I'm always bit anxious to make a lot of noise about these projects.</p><h2 id="design-concepts-and-considerations">Design concepts and considerations</h2><p>Here are a few concepts that I've been thinking about when making this deck</p><h3 id="corners-and-a-symmetry">Corners and (a)symmetry</h3><p>A traditional wisdom in making playing cards is often to make them symmetric over the diagonal. That way, the player can hold or play a card in either orientation and it's easy to read.</p><p>However, in a deck that aims to support multiple games, this is a trade-off that I had to give up early on. With a year of playing with my <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Project 108</a> deck, I've noticed this not to be an issue.</p><p>Fanning cards in a way that only the top left corner is visible, is a very common way to hold cards. That's why the main element ‐ the running numbers ‐ is on the top left of each card. This was a major flaw in Project 108 that was fixed for Potluck.</p><p>The suits & values of traditional playing cards are upside-down so you can play with them on the top left corner by rotating the entire deck 180 degrees.</p><h3 id="twice-the-4-suits-or-8-suits">Twice the 4 suits or 8 suits?</h3><p>A major pondering was whether to have the classic suits (♥, ◆, ♣, ♠️) twice or to bring in custom secondary suits.</p><p>I decided to create a secondary set of suits: stars, coins, triforce and swords in blue & green to add some extra options for playing. This means that you can play games that need up to 8 suits (like the great Parade or Arboretum).</p><p>I tried to incorporate <a href="https://en.wikipedia.org/wiki/Italian_playing_cards#Italian-suited_decks">the Italian suits (coins, cups, sticks and swords)</a> as the secondary suits but it was hard to make sticks and swords distinct enough with the simplified icons that I decided to skip that.</p><h3 id="necessary-trade-offs">Necessary trade-offs</h3><p>There are some very necessary trade-offs that could be annoying to very hardcore players.</p><p>For example, in a game like Coup, it can actually matter a lot if two cards of the same role, eg. Captain, are actually the same or different card. With Potluck style deck, that is impossible to do as there are so many varying elements.</p><p>This can tilt the balance of some deduction-based games a bit but honestly, I'm not too worried. If my game crew would get upset with that, I'm probably in the wrong group at that point anyway.</p><h3 id="leveraging-existing-information">Leveraging existing information</h3><p>The most subtle thing in Potluck is how games integrate elements from each other and from the real games to help game be easier on a glance.</p><p>For example, the Love Letter roles (Guard, Priest, Bishop, etc.) are in cards so that the 7 Signum values on the top right corner match the ranks of those cards in the original Love Letter (1-8).</p><p>Or in Coup, the roles are matched with 7 Signum's colors to match the original colors of the cards in Coup: Captain + Blue, Ambassador + Green and so on.</p><h3 id="an-index-or-no-index">An index or no index?</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/p108-index.png" class="kg-image" alt="A deck of cards from a side profile, showing differently colored stripes on the side" /></figure><p>When I designed <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Project 108</a>, I created a colorful index on the side for quick find of the favorite games. It felt like a great idea during design, it seemed still a great idea when I got the cards.</p><p>But I ended up never using it. It wasn't necessary. I keep the cards sorted in numeric order and have created some custom cheat sheets so I know which numbers I need and it's fast and convenient enough to find the right cards without the index.</p><h2 id="next-month">Next month</h2><p>In the next part of the series, I'll share more about the deck and its cards itself.</p>
Guide to landing your first dev job
2023-01-25T00:00:00Z
https://hamatti.org/posts/guide-to-landing-your-first-dev-job/
<h2 id="introduction">Introduction</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/00-introduction.png" class="kg-image" alt="Introduction" /></figure><p>Are you a student, hobbyist, junior developer or career-changer looking to find your first developer job? The season for applying to summer jobs in Finland is upon us so this article aims to guide you through some short- and long-term things that help you land your first position.</p><h3 id="couple-of-notes-before-we-start">Couple of notes before we start</h3><p>Since my blog's main audience is very international and varied, I want to preface this post by saying that this blog post is mainly aimed for and written from the perspective of <strong>developer jobs in the Finnish market</strong>. Some of the things may apply to your local market as well but it's very likely that not everything works the same way.</p><p>Also, there are <strong>many ways to find jobs and to showcase your skills</strong>. This is one account from what I have learned over the years but not the only one. I'm a "home-grown" developer who was a hobbyist first and then become a professional and that is just one experience so if your background or way you'd like to do things doesn't match this, don't worry!</p><p>Giving advice on job hunt has always felt really difficult for me. At the same time, I have a lot of insight into it through the work I've done and juniors I've helped but I've learned through it that <strong>for every advice, there's a recruiter who thinks the opposite</strong>. There are no universal truths when it comes to finding jobs, applying to them, writing CVs and applications, building portfolio and so on. There's a lot of luck involved in your application ending up on a table of a recruiter who values the things you do and the ways you are doing them.</p><p>I'm using the term <strong>"junior developer" or "junior" to describe someone early in their software development career</strong>. You might be a senior in other fields if you're a career changer, you might be a hobbyist or a student – or something else. Some companies might call you an intern or a trainee. It's just easier to use a blanket term to cover it all than repeating all the possible situations one might be every time.</p><p>With that said, let's get started!</p><h2 id="finding-the-companies-jobs">Finding the companies & jobs</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/01-find-companies.png" class="kg-image" alt="Find the companies" /></figure><p>When you're looking for your first position, more is more. Unless you have very strong experience through hobbies or open source projects, you're in a situation where a lot of people with similar experience and skill levels are applying for a limited amount of positions in your area. That sounds a bit depressing but what I'm saying is that you should not be too picky when applying.</p><p>That said, if you've heard bad things about a company and there are big red flags, by all means, skip it. No job is worth that and there's plenty of good ones in the market.</p><h3 id="don-t-wait-for-the-job-ad">Don't wait for the job ad</h3><p>My first advice is to <strong>apply to companies even if they don't have an open position</strong> advertised anywhere. Maybe they haven't started their summer job campaign yet and you're ahead of the pack. Or maybe they hadn't thought about hiring summer employees or juniors but you could convince them that you're worth their time. Worst case scenario is that they are not hiring and even then, you may seem like an active and interested developer who might be considered in the future.</p><p>Many companies these day have some form of "Open application" in their career page because IT industry is pretty much always hiring these days. I've sent more open applications in my life than applications to advertised positions.</p><p>A good approach is also to list all the companies you know of or are interested in, rather than starting your search through job ad platforms. Prioritize companies you'd like to work in because if you get a job in those, you'll probably be happier.</p><h3 id="team-up-with-other-juniors">Team up with other juniors</h3><p>Do you know other people in similar situation? If you're a student, you probably have some student mates you could team up with to do the discovery process. If you're a career-switcher or a hobbyist not in an organized group with others in the same situation, reach out to different communities (I'll talk more about these a bit later in the networking/community section below) to find people to partner up with.</p><p>You can create a shared document: eg. a spreadsheet on Google Drive, an Etherpad or a Notion page to track companies your group has found and information about them, including links to their websites, job ads and so on. When I was looking for my first job back in university, everybody in my friend group had an interview in the same company within like two weeks or so. One or maybe two got a summer job there and ended up staying for years.</p><h3 id="where-to-find-them">Where to find them?</h3><p>If you are studying, keep an eye on the recruitment board of your student organization. Companies are often advertising there for their junior positions. The trick is to not only look for positions that are posted after you start looking for but dive into the archives for the past couple of years and list every company there. Similarly, companies sponsor the student organizations or their events – or other university/college events like career fairs. Go through past events and sponsorships and add them to your list.</p><p>Even if you're not a student with direct access to those groups, check the websites of student organizations in your area anyway. Often these are publicly listed. For example, my alma mater student organization <a href="https://www.asteriski.fi/">Asteriski</a> has a nice list of local companies listed as sponsors. For example in Turku, Finland alone, there's quite a few of these student organizations: Asteriski, Digit, DaTe, Infå, TIO and probably some more that have popped up since my student years. Don't limit your search to only your direct social group.</p><p>And student organizations are not the only places: local meetups, tech conferences and other organizations often organize activity sponsored by tech companies. <a href="http://turkufrontend.fi/">Our Turku ❤️ Frontend</a> community for example has 22 local tech companies listed as sponsors on our website. And we're only one in the bunch. Others might not have them listed on a website but you can take a look at past events on meetup sites like Meetup.com or Meetabit.com. To continue with the Turku theme, communities like <a href="https://twigthecode.com/">Twig the Code</a>, <a href="https://meetabit.com/communities/aurajoki-overflow">Aurajoki Overflow</a>, <a href="http://turkusec.fi/">TurkuSec</a>, <a href="https://meetabit.com/communities/turku-py">turku.py</a> or <a href="https://mimmitkoodaa.ohjelmistoebusiness.fi/">Mimmit Koodaa</a> work with a lot of tech companies. Same applies to events in your city as well.</p><p>And you're likely to know companies from other circumstances through: someone once said that "Every company is a software company" these days because everyone needs software to run their business. So you can look into companies that work on other industries as well: banks, healthcare, education and many other industries have teams of developers working for companies.</p><p>Write every company name that you find or run into to a list somewhere. It'll be handy, not only for your first job but in the long run as well.</p><p>Occasionally, others are also compiling lists and sharing them publicly! For example, in Finland <a href="https://talented.fi/en/blog/trainee-programs-for-junior-developers-in-finland/">Talented just published a collection of companies and their trainee programs</a> and Juho has been collecting similar things on <a href="https://akatemiaohjelmat.github.io/">this open sourced collection</a>. </p><h2 id="how-to-showcase-your-skills">How to showcase your skills</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/02-showcase-your-skills.png" class="kg-image" alt="Showcase your skills" /></figure><p>The big challenge most junior developers run into is how to show what you can do. When you don't have a long work experience in the industry and most people in the same situation have somewhat similar paperwork (for example, students who've studied the same studies). It's very difficult for companies to figure out what differentiates you from the others.</p><p>You don't always have a choice between these different options, sometimes companies just tell you what they want and don't accept alternatives. But it's always worth asking: "I have some hobby projects I've built, maybe I could show some of those instead of a homework assignment?" or "I can't dedicate hours in the evenings besides work and family responsibilities to do projects, could I showcase my skills in an interview setting?"</p><h3 id="portfolio-of-hobby-side-project-s-">Portfolio of hobby/side project(s)</h3><p>One way to do that is to have hobby projects. These are projects that can be used to show experience before you have work experience. The downside of them as a general advice is that not everyone has time to work on their personal projects and that's perfectly fine too. We'll talk about the other options later.</p><h4 id="how-to-come-up-with-ideas-for-a-project">How to come up with ideas for a project?</h4><p>The more experience and skills you have, the easier it is to come up with ideas for what to build. You have gained understanding of what is possible and ran into ideas for what to build. But what if you don't have any ideas? Here are a few seeds of thought to get you started. If you already have built something, you can skip to the next subsection titled "How to showcase your portfolio projects?".</p><p><strong>Build something you need. </strong>I find these to often be the best projects: you don't need to artificially come up with ideas for features because they stem from your own needs. Extra benefit is that in addition to gaining a portfolio project, you'll gain something that is inherently beneficial to you.</p><p>One of the examples from my hobby project library is <a href="https://hamatti.github.io/nhl-235/">235</a>, a command-line tool built with Rust that displays NHL results on the command-line. It's a tool that I built for myself to solve a need that I had and offered a great opportunity <a href="https://hamatti.org/posts/learning-rust-pattern-matching/">to learn a new language</a> and to have a project I can showcase when applying for jobs. If it is helpful for anyone else too, that's just a bonus.</p><p><strong>Build something for a non-profit or community. </strong>Doing work for free is not a great proposition. There may be short-term individual benefits of launching a career but it's only available to those who can afford it and it does the industry a huge disservice. But using your newly acquired skills in software development can be put to good use with non-profits and communities you're involved in.</p><p>Many communities run by people contributing whatever skills and time they have for the shared cause. And there's a lot that can be improved with software that there's no real budget to buy a solution from experienced professionals but are perfect for a junior's hobby project.</p><p>In my own hobby project library there's <a href="https://glc-checker.netlify.app/">Gym Leader Challenge Decklist Validator</a> which is a tool I built for the Pokemon TCG player community that I'm part of to help everyone out. I've also built websites and web apps very early in my hobby journey for other communities and I attribute most of my learning process and early career growth to those.</p><p>Finally, you can <strong>replicate something that exists.</strong> If you don't have any needs yourself or don't quite know what to build for your community, a good way to build a portfolio project is to take something that exists (for example, a photograph filter system like ones at Instagram and other services) and try to replicate the functionality without looking at any of the code.</p><p>It's important to be open and transparent about the fact that you are doing this as a portfolio showcase and that it's not your idea. </p><p>This approach lifts the burden of coming up with product design for something completely new – a skill not required by a junior developer – and you can focus on writing code and implementing something. The outcome is a code sample and example project you can show and discuss (more of that later) to your potential employer.</p><h4 id="how-to-showcase-your-portfolio-projects">How to showcase your portfolio projects?</h4><p>When you have built something, you have code. Even though code is the necessary part for a functioning software, it's not enough to get the best out of your project.</p><p>If you send a company your CV, application and a link to a repository that only has your code, it's likely nobody will spend more than a few seconds looking at your project. It's daunting and nearly impossible to figure things out from the code itself. </p><p>Having a list of multiple projects can be a disservice to yourself too. Since sites like GitHub are not only a portfolio site but a tool that we use to build our software, it's likely that over time, you'll end up with many projects there. You want to direct the reader of your page into the best and most important ones. </p><p>Let's look at some things you can do to make your case better.</p><p><strong>Make the code available.</strong> Start by putting your code somewhere online. Most common places are <a href="https://github.com/">GitHub</a>, <a href="https://about.gitlab.com/">GitLab</a> or other code hosting site. Sending zip files as email attachment these days is not probably going to land well.</p><p><strong>Write a good readme. </strong>Before anyone sees your code, they should get a good understanding of what your project is about. </p><ul><li>Give your project a name (doesn't need to be good or creative), </li><li>Write a short description of what it is and what it does (for example, from my 235 project: <em>"235 is a CLI tool with very simple design: one command that displays currently on-going or previous round's NHL games with results and goal scorers.")</em></li><li>Write about the technologies you've used</li><li>Write instructions for how to run your project in development mode (this is mostly for you, you'll thank me later)</li><li>Optionally, write about things that you've learned doing this project</li></ul><p>Don't hesitate to use screenshots or short videos as examples and if you have a live demo somewhere, link to that!</p><p>If you don't know where to start, there are a lot of templates like <a href="https://github.com/othneildrew/Best-README-Template">Othneil Drew's Best README Template</a> that help you get started.</p><p><strong>If you only do one thing from this portfolio section, do this one. </strong>A good readme will do wonders in getting people interested in what you've built and provide a way for them to understand it and navigate it.</p><p>Sharing your learnings is very valuable, especially for juniors. I'll talk more about that in the interview section of this post but it can be also written into the readme of the project.</p><p><strong>Build a "product page" for your project. </strong>In addition to the readme, you can also treat your project as a real product even if you don't plan to sell it to anyone or gain users.</p><p>Take a look at <a href="https://tailwindcss.com/">Tailwind CSS's product page</a> for example. It clearly communicates what it is, how to use it and so on.</p><p>You can use for example <a href="https://pages.github.com/">GitHub Pages</a> to host your product page directly from your project for no cost or <a href="https://www.netlify.com/">Netlify</a> to host your website for free.</p><p>If you have multiple projects, my advice is to <strong>pick one project as your best showcase</strong>. Pick the one you're most proud of or most would likely to talk about and focus your effort on making that look great and interesting.</p><h3 id="home-work-projects">"Home work" projects</h3><p>Some companies, especially if they are hiring more than one or two juniors through a summer job campaign or similar, have some sort of projects that you do on your own before you apply. And quite often, if they don't, you can ask for one if you don't have any existing side projects. In my opinion, the best companies let you choose between an existing project and their job application period but not everyone does that.</p><p>These are usually small projects that ask you to implement something with relevant technologies to the company you are applying to. They are meant to be projects that can be finished in an afternoon or a day (or some claim, in a few hours) but I've seen so much variety in how realistic those are. They are often written by experienced developers who estimate how long it would take them and add a bit more and that's often not super good estimate. I know because I've done that mistake myself when creating these sorts of assignments for applicants and have failed at estimating them.</p><p>Do your best at following the instructions. If you have time, you can add some flair to stand out but don't stress it too much. Don't hesitate to ask questions from the company if there's something you don't understand or are not sure about in the instructions or what is expected.</p><h3 id="live-interviews">Live interviews</h3><p>Sometimes it's not feasible to work on projects on your own time, especially if you are applying to multiple places and have other responsibilities in your life (taking care of family, another job, studies etc).</p><p>Another way to show your skills is to do a live coding exercise during an interview. These are often used and often not much liked by developers but their benefit is that it doesn't take hours of your free-time so I think they are great for some situations.</p><p>I'll talk more about the interviews in general and tips for live coding exercises bit later in the post.</p><h2 id="cv-resume-cover-letter">CV/Resume & Cover letter</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/03-cv-application.png" class="kg-image" /></figure><p>(I use terms CV and resume interchangeably here. It's a reference sheet of your experience.)</p><p>This section is the hardest one for me to give advice on because it's the one that has no right answers. For every advice someone gives, there's a recruiter who thinks the opposite and considers following that advice negatively. So take all of these with a grain of salt.</p><p>A job application often consists of two parts (+ the portfolio mentioned above): a <strong>CV/resume</strong> that lists your work experience, education and possibly skills and a <strong>cover letter</strong> that explains why you are applying to that specific position and why you'd be a great fit.</p><h3 id="cv-resume">CV / resume</h3><p>Writing a CV is hard when you don't have experience in the field. Before you start writing your CV, think about this: what are all the things that you've done in your life that could be helpful for this job.</p><p>There are many opinions about what to include but the above perspective has been my guiding light. Whether something is a full-time job, a non-profit community project or a hobby, it doesn't really matter. What matters is what experience and skills I've gained from it.</p><p>My suggestion is to put the <strong>most relevant and recent</strong> to the top of your CV. Usually work experience is more relevant than studies, so I most often list those in the top. For a student, this is especially true if you're applying locally in your area where most other applicants are from the same schools or universities.</p><p>When you have limited (relevant) work experience, you need to be a bit creative. If you've been active in your student organization (maybe as a board member or a tutor), absolutely write that into your CV. Have you built smaller projects for a hobby organization your part of? Into your CV it goes.</p><p>Some disagree with this, but I don't consider CV as a pure reference guide to your employment and academic history. It's a document that you use to present the best possible truthful image of yourself to potential employers. It means that the CV isn't always the same between all the companies you apply to.</p><p>If you have experience in being a cashier on a supermarket and you apply to a company building point-of-sale systems, absolutely make that experience an important factor in your CV. It brings valuable first-hand experience into the team that might not have any.</p><h3 id="cover-letter">Cover letter</h3><p>Here you should explain why you're applying, what you can do and what you want to do. I consider these all things that cannot be derived from the CV as that only describes your past – not your future desires.</p><p>A former colleague at Futurice wrote a blog post <a href="https://futurice.com/blog/5-steps-to-a-great-application">5 steps to a great application</a> a decade ago but it still holds a lot of wisdom. It's one company's (or more specifically, one person's) view but in my experience, following its tips will be beneficial in general.</p><p>Like the CV, the cover letter should be specific to each position. In it, you should put the spotlight and guide the reader's attention to the things that you think are the best of you and your work.</p><p>Early into my career, I built websites and small web apps as my cover letter. I wanted to show, not tell, what my skills were and that I understood where I was applying to. When I was writing a job application to Spotify for example, I made my cover letter a website that mimiced the layout of their desktop client (with real-time updating feed of what I was listing in Spotify). </p><p>It's not necessary to go that far but it's an example of thinking outside the box, getting the attention of the person reading the applications and sometimes it works, sometimes it doesn't.</p><p>What kind of cover letter and CV works for any given company depends completely on the company and the individual person reading your application. That's why it's so hard to give clear advice. I've always tried to think of ways that can help me gain their attention in a positive way that showcases my skills in the best possible way.</p><p><strong>The goal of the CV & cover letter is to get you an interview.</strong></p><p>It's then the interview(s) where you prove your case.</p><h2 id="interviews">Interviews</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/06-interviews.png" class="kg-image" /></figure><p>This section is not an all-out guide to interviewing. Instead, I want to focus on one part: how to use your portfolio projects to the max.</p><p>One reason I really like having a hobby project in my portfolio when applying for jobs is that it usually enables me to drive the discussion to the direction I'm most comfortable with. I know my project inside and out: the why, the how and all that jazz. By bringing those projects up in the interview, I can explain my skills and what I've done in a way that I know the answers to up front instead of having to rely on coming up with good answers to surprising questions at the spot.</p><p>The reason this is so valuable is that job interviews are stressful. Even people with a lot of experience who can build amazing things can freeze up and forget simple stuff in the interviews. By having at least one path of discussion that you know well through experience helps you make sure you are able to showcase your skills and what you know.</p><p>And those projects don't have to be super impressive either. Even less so for a junior. A main concern for an interviewer when interviewing a bunch of juniors with similar educational history and no work experience is figuring out who knows what. So showing a project that you've built (and decided to build yourself, that's why school projects with given assignments are not the most effective but work if you don't have anything else) is very good.</p><p>Throughout my career, I've used different projects as my main discussion points in interviews. Once I had a very rudimentary prototype for a personal budgeting app that I had built few months earlier to discuss why I built it, what technologies I used, why I choe Vue.js + Firebase and what I learned from the project. It wasn't finished, it wasn't polished but it was a good discussion starter.</p><p><strong>So what can you talk about in your project that showcases your skills and understanding?</strong></p><p>Here's a non-exhaustive list (but you don't have to have great answers to them all)</p><ul><li>Why did you build your project? Talk about what sparked the idea to start building this (and saying: to learn this new tech is perfectly fine answer) and how did you design and decide what features to have in it.</li><li>What technologies you used and why? This allows you to expand further than just some code to explain your understanding of underlying technologies, why some might be more suitable than others (once again, saying "that's the language I know best" is perfectly fine answer).</li><li>What did you learn? It shows that you've learned new things and are able to recognize them and reflect on them. Crucial skill in software development.</li><li>What would you do differently? Have you ever looked at your old code and cringed a bit? That's a good sign, shows you've improved! Being able to understand what you would do differently, even if you haven't implemented those changes into the code, is a good sign and can reveal a lot about your understanding of software development.</li><li>What are you most proud of in this project? When someone talks about things they are excited about and proud of, regardless of the topic, it's engaging. You are in the driving seat so pick the things that you're most excited about and talk about them. Maybe you were able to optimize a SQL query to speed up your program execution by 50%: definitely worth talking about. Or maybe you made a really nice CSS animation to your frontend: share it!</li></ul><p>The key to these questions is that they are things you can talk about even when not directly asked about. If an interview asks you to talk about a project that you mentioned in your cover letter, think about these as a framing for your answer. Or if they don't speficially ask about your project, you can ask if you could talk about it. <em>"I don't have work experience in development but I've built this project, could I show it to you and talk about it?"</em></p><p>Don't think that you'd need some miraculously amazing answers to any of these. Saying that you built something because you wanted to learn something new or that you chose a technology because that's what you know best or saying "I don't know" to any question are all perfectly fine.</p><h2 id="long-term-networking-blogging">Long-term: networking & blogging</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/04-networking-and-blogging.png" class="kg-image" alt="""" /></figure><p>All above is what you can do right now when applying and interviewing for jobs. I put it out there up front because it's the more pressing one. If you want to have a long-lasting positive impact on your career, continue reading. These are all non-necessary ones: in fact, most developers who have great careers don't do them. But I've found them incredibly useful and beneficial.</p><p>Let's talk about my expertise: networking, communities and blogging.</p><h3 id="networking-and-communities">Networking and communities</h3><p>Networking may have a bit negative connotation but even if you're like "yikes, not for me", keep reading. Networking isn't (only) going out to events and handing out business cards to everyone you meet or maxing out your connections count in LinkedIn. In fact, I personally don't care about either.</p><p>Networking is things you do together with other people, and then some. For a student, a great place to start networking is your student organization and fellow students in your program. It can be getting involved in student guild or just hanging out in the events, studying together and making friends.</p><p>When I was a student and someone gave me this advice, I thought "what's the point of networking with people who are in the same situation as me". Now, over a decade later, all those friends I made during the studies are in senior and lead roles in companies all across the country and even the world. Whenever I apply to a dev job, I see them in these companies and end up having interviews with them.</p><p>Networking also isn't about "only making friends to benefit from them" or taking advantage of anyone. It's about doing things together, helping each other out and being a good person. The benefits in career are just a positive side effect.</p><p>Last year, I wrote a <a href="https://hamatti.org/posts/developers-guide-to-communities/">Developer's Guide to Communities</a> that goes more in-depth into all of this. </p><h3 id="blogging">Blogging</h3><p>Writing a blog is such a powerful tool for a developer: it helps you improve your craft, it improves your communication skills, it can help as a refactoring tool, it builds a body of work that you can link to in your job application and sometimes you get great opportunities because people have been reading your blog for years.</p><p>One big mental hurdle many (me included early in my career) have about blogging is: "I'm not good enough to blog about this" or "Someone else has already written about it". Try to get rid of that thought. Frame it in your head as "I'm documenting what I've learned" and if your writing is useful to someone else, that's just a bonus.</p><p>Whenever you create something or learn something new, write a short blog post about it. Post it on your blog (more tips for getting started in the blog posts linked below), share it with your developer friends.</p><p>Write first and foremost for yourself: to help you make sure you understand the concepts you're learning and working on, to document things (I regularly go back to <a href="https://hamatti.org/posts/how-to-scrape-website-with-python-beautifulsoup/">How to scrape a website with Python & BeautifulSoup</a> to remind myself how to do it) and to keep a log book of what's going on in your professional journey.</p><p>I've been blogging on/off since 2013, more seriously and actively since 2019. I love looking back at my old posts, reading through to see how much I've improved both as a writer and as a developer and I'm so glad that the material is out there. </p><p>I have written more about blogging for developers if you want to dig deeper:</p><ul><li><a href="https://hamatti.org/posts/you-should-start-a-blog-today/">You should start a blog today</a></li><li><a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed/">Your blog should have a RSS feed</a></li><li><a href="https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/">Blogging is my new favorite refactoring tool</a></li><li><a href="https://hamatti.org/posts/over-a-year-of-weekly-blogging/">Over a year of weekly blogging</a></li><li><a href="https://hamatti.org/posts/learning-in-public/">Learning in Public</a></li></ul><h2 id="wrap-up">Wrap up</h2><p>I hope this guide gives you ideas, inspiration and some pointers that will help you in your journey to your first job. <strong>Getting the first job in the industry is usually the most difficult one</strong>. After that, you start to have experience, references and more skills that make it easier in the future.</p><p>You'll inevitably get rejected a lot. Don't get discouraged. You'll get there one day. <strong>I'm cheering for you!</strong> You probably won't get good feedback from the companies on how to improve your application or interviews: sometimes it's because companies don't take time to write it but sometimes also because there's limited amount of junior spots and they had to pick one or two people.</p><p>Good luck!</p>
My Pokemon apps updated for Crown Zenith
2023-01-20T00:00:00Z
https://hamatti.org/posts/my-pokemon-apps-updated-for-crown-zenith/
<p>Pokemon's new TCG set <a href="https://tcg.pokemon.com/en-us/expansions/crown-zenith/">Crown Zenith</a> dropped today and for me, these Fridays always mean work to update my projects.</p><h2 id="gym-leader-challenge-decklist-validator">Gym Leader Challenge Decklist Validator</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/gym-leader-challenge-deck-validator-header.png" class="kg-image" alt="Gym Leader Challenge Deck Validator" /></figure><p>One day I'll learn to come up with names for projects that are not 100 characters long but today is not that day. <a href="https://glc-checker.netlify.app/">Gym Leader Challenge Decklist Validator</a> is a web app that allows players to submit their <a href="https://gymleaderchallenge.com/">Gym Leader Challenge</a> decklists and see if those are valid for the format.</p><p>I think <a href="https://hamatti.org/gaming/the-best-way-to-play-pokemon-tcg/">GLC is the best way to play Pokemon TCG these days</a> and that's why I built this app. With singleton format, the decklists have a lot of different cards and it can be easy to make mistakes.</p><p>With the Crown Zenith update today, it supports all cards from Black and White base set to Sword & Shield's final set – that's every set legal in the format.</p><h2 id="pokemon-tcg-card-viewer-for-firefox">Pokemon TCG Card Viewer for Firefox</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2023/01/image.png" class="kg-image" alt="Pokemon TCG Card Viewer" /></figure><p>This project is another one with quite a mouthful for a name but it also packs a bunch in its small form factor. It's a <a href="https://addons.mozilla.org/en-GB/firefox/addon/pokemon-tcg-card-viewer/">Firefox extension</a> that changes cryptic looking deck lists into a format where you can see the actual card images on hover. With its version 1.3.0 (and a bug fix of 1.3.1), it now works with Crown Zenith cards and supports all cards from the oldest Pokemon TCG Online supported set, HeartGold & SoulSilver onwards.</p><p>Head over to <a href="https://addons.mozilla.org/en-GB/firefox/addon/pokemon-tcg-card-viewer/">Firefox add-on store</a> and install it to your Firefox to test it out!</p><h2 id="surprise-we-changed-the-format">"Surprise, we changed the format"</h2><p>After I made the initial updates, I noticed that Pokemon had changed the decklist export format in the Pokemon TCG Online. Where a card used to be specified with a set code (like <code>CRZ</code> for Crown Zenith) and a number (like <code>12</code>), it now changed to left-padding the number with leading zeroes: turning <code>12</code> into <code>012</code>.</p><p>Luckily the change I had to make wasn't a big one, I was little bit disappointed that they did not notify users about this change in <a href="https://forums.pokemontcg.com/topic/80713-version-2950-patch-notes-january-17-2023/">their patch notes for the newest version</a>. If they had, it would have saved me from a bug fix patch version after noticing the issue too late. I tested against hand-picked values rather than exports – a lesson learned there for the future.</p>
I'm leaving Mozilla
2023-01-18T00:00:00Z
https://hamatti.org/posts/im-leaving-mozilla/
<p><strong>I resigned from Mozilla.</strong></p><p>When I first heard about <a href="https://blog.mozilla.org/addons/2022/09/01/hello-from-the-new-developer-advocate/">this Developer Advocate position at Mozilla</a> in March of 2022, it got me interested. Discussing it with a friend who already worked at Mozilla got me excited and the interview process, while not perfect and without its flaws, convinced me that the Firefox Add-ons team at Mozilla was one I wanted to join.</p><p>I finally found a job that enabled me to move to Berlin. So I packed up my bags, resigned from my old job, moved to Berlin and started fresh. For a while, it was magical.</p><p>On paper, everything was great. Mozilla is one of those companies that has a mission I truly believe in, it's full of great people who are passionate about the things that I was passionate about and the opportunities in community building for the add-ons developers were plentiful and challenging.</p><p><strong>The only thing that wasn't great was me.</strong></p><p>Have you ever played a board or video game where, if you run out of resources, you could pay the required cost with another resource but with a higher cost of 2-to-1 or 3-to-1? Maybe you haven't but that's the best analogue I could come up with how I feel.</p><p>For half a decade, I've been pushing myself hard and pretty much living and working at the far edges of the comfort zone. I've achieved a lot of stuff I could have never dreamed of. But it has all happened at the expense. Now it's payback time. I've run out of resources to pay the cost.</p><p>I've been mentally overwhelmed and exhausted since 2014 or 2015 on a constant pace. I've always found that little bit of extra energy somewhere. Even a few burnouts didn't stop me, even though they should have. I was always worried – and to be honest, still am – about succeeding at work. So I've kept pushing and pushing.</p><p>My physical health is not great. I've gained a lot of weight over the years because I ate badly and ordered takeaway daily because I needed all the mental energy to battle the feeling of being overwhelmed. I've gone weeks without proper sleep because of all the stress as I've been scraping the bottom of the barrel just to keep my head above the water at work life. It turns out, I can't do that forever. Who would have guessed?</p><p>Often, I have quoted <a href="https://www.youtube.com/watch?v=ji5_MqicxSo">the great talk by Randy Pausch</a>:</p><blockquote>Brick walls are there for a reason. They are there to let you show how much you want something.</blockquote><p>I'm starting to feel, that this time, the brick wall is there for me to have something to lean on while I rest. </p><h2 id="what-s-next">What's next?</h2><p>I'm packing my bags and moving back home. I'm <a href="https://hamatti.org/hire">now looking for a job in Turku</a> and I would like to find a job where I could be a cog in the machinery, so to speak. I don't have the capacity to every day come up with new ideas, implement them and justify them to the leadership so most community positions are unfortunately out of the window.</p><p>I would like to find a team where I could put my software development skills to a good use while not having too many responsibilities. I want to join an in-house team (no consulting or lead positions) where I can build software and maybe help juniors while doing that. I'm mostly comfortable with Javascript, Python and PHP.</p><p>And all the other energy I'll direct to getting my life back on track. Finding ways to recover and to build healthy day-to-day habits. To cook, to go for walks, to spend time with friends.</p><p>I will keep organizing Turku ❤️ Frontend meetups and running the Syntax Error newsletter but mostly everything else will be on a hold until I've gotten better.</p>
Me and the elves - Advent of Code 2022 retrospective
2023-01-11T00:00:00Z
https://hamatti.org/posts/me-and-the-elves-advent-of-code-2022-retrospective/
<p>Another year of Advent of Code is over and it's time for a retrospective. In 2022, I finished the year with 38 stars.</p><h2 id="surprisingly-i-ve-grown-as-a-developer">Surprisingly, I've grown as a developer</h2><p>In the past year and a bit, since last year's Advent of Code, I've been exploring extra puzzles from previous years, starting with the first (2015). So when this year rolled in, I had more experience in solving these with my chosen tool set - Python and Jupyter Notebooks.</p><p>This year, I could totally see the impact this experience has brought. There are a lot of puzzles (for example, <a href="https://hamatti.org/adventofcode/2022/day_13/">day 13</a>) that would have been triggered a "yikes" reaction from me before but this year was "oh cool" reaction. I've also been able to solve these puzzles way easier than in previous years which has been fun.</p><p>Few specific areas that I've gotten more comfortable with are my usual weaknesses: recursion and trees. There's still one major gap and that's math but that's something I would have to look into other sources to get better at it.</p><p>What I mean by math is the ability to recognize what mathematical concept I should apply and how. For example, with <a href="https://hamatti.org/adventofcode/2022/day_11/">day 11</a>, I immediately noticed that math is needed but I probably would have never figured out alone on my own that I need to find the least common multiplier. Once I was nudged to the right direction by the help from community, I was able to plug it in and get the puzzle solved.</p><p>One thing I really like to see is that I've become better at refactoring my solutions into elegant and easier to read code. There were some days when I was traveling or busy with work and didn't have time to focus on that but for example <a href="https://hamatti.org/adventofcode/2022/day_13/">day 13</a> ended up with some of the most beautiful code I've probably ever written.</p><p>And I'd like to think my explanations and associated writing in general has improved but that's a question you'd need to ask someone else. I did get a lot of positive and encouraging feedback from readers this year so I'm happy.</p><h2 id="now-with-more-doctest">Now with more doctest</h2><p>I haven't really been writing much tests for Advent of Code puzzles in the past. Last summer, I took <a href="https://hamatti.org/posts/unit-test-your-python-code-in-jupyter-notebooks/">a look at options in testing tooling for Jupyter Notebooks</a> and this year, partly inspired by <a href="https://github.com/pauloxnet/adventofcode/tree/main/aoc2022">Paolo Melchiorre's use of doctest</a>, decided to go with it where necessary.</p><p>I'm not testing every function but whenever I run into issues or see upfront that things might be harder than I'm comfortable solving, I do test-driven development (TDD) with doctests. I'm not still sure if I like doctest that much. There are benefits and downsides that I went into more depth with the blog post linked above. It makes browsing through the code harder when a 10-line function might have 20+ lines of docstring. </p><p>For the first half of December, I struggled with debugging because every <code>print</code> inside a function makes every test fail (as doctest compares against stdout). So I did a lot of cut-paste to a separate cell or file to only run the messed up test and so on.</p><p>On the 13th, I learned that I can print to <code>sys.stderr</code> and that will solve all the issues I had. Now it's much more enjoyable to use doctest.</p><p>Especially for functions that use more complex operations or recursion, doing TDD with doctest often helps the complexity to build up manageably and without even noticing as I can go case by case and at some point, everything just works and I can refactor.</p><p>With Jupyter + doctest combo, changing the interface of functions became a pain though. If I needed to add another return value, I'd have to painstakingly go through all the doctests and change them. With proper code editor support, that could have been easier. VS Code supposedly <a href="https://code.visualstudio.com/docs/datascience/jupyter-notebooks">supports developing Jupyter notebooks</a> but I haven't tested that out yet, I've always used notebooks via a browser. Maybe in 2023 I'll explore that option.</p><h2 id="community-still-rocks">Community still rocks</h2><p>Every time I write or talk about Advent of Code anywhere, I <a href="https://hamatti.org/posts/advent-of-code-2021-retrospective/">keep saying the same thing</a>: it's so much fun to solve these puzzles with fellow community members. I usually try to solve mine first alone and if successful, I'll celebrate and share with the community. If I get stuck or don't understand something, I'll reach out to people and always find people willing to help.</p><p>And it's so cool to see other people's solutions in so many different languages and learning from those. And seeing how other people solve and approach the same puzzles with the same tools but in a widely different manner.</p><p>So big thanks to my fellow puzzle solvers in Koodiklinikka, Asteriski, Lumberchill, Mozilla and Mastodon communities. </p><h2 id="december-full-of-travel-and-work">December full of travel and work</h2><p>In 2021, I was on a burnout leave for the entire month and Advent of Code was the perfect distraction from everything. This year, I was bit worried I might not be able to participate fully as I had 15 travel days within these 25 days + a job I started recently.</p><p>Turns out, it wasn't much of a problem at all! With my improved coding skills and a relaxed attitude towards those days when I knew I couldn't do as much refactoring or explaining, I was able to solve the problems first thing in the morning before heading to work or events. And for those travel days when I was in a train or a ferry, I used my limited internet availability in a smart way and was able to focus on the development with local environment.</p><p>I did end up skipping most of the final days due to spending the days with family and prioritizing that over being stuck with the obsession to complete these puzzles. That proved to be a fantastic idea: I felt zero stress or anxiety about the need to reach 50 stars.</p><p>My workflow for this year was:</p><ol><li>Solve the problem, get the stars</li><li>Come back to the solution and do first pass of writing explanation</li><li>Refactor and polish code and change or add explanations accordingly</li><li>While discussing with others, new ideas or approaches pop up</li><li>Refactor those into the original solution or write an appendix</li></ol><p>There were a few days when the puzzle was either too hard or the day was too busy but the amount of those was way smaller than I had expected. <a href="https://adventofcode.com/2022/day/22">Day 22</a> gets a special mention though: I spent 2 hours manually figuring out where the next entry point is, only to notice that the actual input was a different shape than the example one. I just gave up with the second star immediately after that.</p><h2 id="future-improvement-ideas">Future improvement ideas</h2><p>What's a retrospective if there's no improvement ideas and action items?</p><p>There's one big thing I'd like to find a solution for before next year: a way to show my progress through each puzzle. And preferably in a way that doesn't require the reader to go through git diffs. I think<a href="https://fasterthanli.me/series/advent-of-code-2022"> Amos is doing quite a nice series with Rust</a> that I've been reading to learn Rust and to pick up inspiration for my explanations.</p><p>When a reader goes to <a href="https://hamatti.org/adventofcode/2022/">my Advent of Code page</a> and starts reading through the solutions, they are faced with the refactored, polished solution. And while that is okay, for the enhanced learning opportunities I'd like to find a way to show my code in different stages and explain the changes and how I ended up with those changes and why I think they improve the code.</p>
Introducing: Syntax Error
2023-01-04T00:00:00Z
https://hamatti.org/posts/introducing-syntax-error/
<p>We've all been there, right? You write some code and run it. But it doesn't work. Maybe it's the compiler or interpreter that is complaining or maybe it runs but doesn't work as intended. You start sweating a bit, since it's Friday afternoon and you'd rather be on your way home than fixing that nasty bug.</p><p>Debugging can be a stressful situation but it doesn't necessarily need to be. For myself, debugging is one of the parts of software development that I actually enjoy a lot. It lets me role play Sherlock Holmes, dive deep into the code and using a variety of techniques and tools, figure out why the computer is not doing what we wish it to do.</p><p>Now I want to share what I've learned with you so you could stress a bit less in your life. <strong>Syntax Error</strong> is a monthly newsletter that helps software developers turn a stressful situation into a joyful exploration.</p><p><strong>The first issue of Syntax Error comes out Friday, February 17th.</strong></p><p>Sign up now via <a href="https://www.syntaxerror.tech/#/portal">https://www.syntaxerror.tech/#/portal</a> </p><p>(PS. There's also <a href="https://www.syntaxerror.tech/rss/">an RSS feed</a> option for those who don't like emails.)</p><h2 id="a-kind-ask-to-you-my-friends">A kind ask to you my friends</h2><p>You support with getting this newsletter off the ground is huge for me. So if you are a developer, I kindly ask if you could subscribe and share it with your developer friends.</p><p>I'm not very good at marketing and I hate pushing things on people's faces. So maybe if everyone invites one or two of their friends on, we can build something beautiful together.</p><p>If you've been enjoying my blog posts or conference talks, I'm quite confident that you'll like Syntax Error too.</p>
Year in Review 2022
2022-12-28T00:00:00Z
https://hamatti.org/posts/year-in-review-2022/
<p>It's time for another journal reflecting on 2022 and looking into 2023.</p>
<h2 id="introduction">Introduction</h2>
<p>
This blog post is primarily written for future me. I like documenting my
journey and have found going back to earlier years' review notes really
enjoyable. However, you're free to join the ride! I hope this post will open
up to you a bit more about who I am beyond all the tech and community posts
and board game projects. I've also learned a lot about my personal growth from
the evolution of my writing style and the way I do retrospection.
</p>
<p>
If you're interested in doing something similar, I find
<a href="https://yearcompass.com/">Year Compass</a> a great free tool to guide
the retrospective and future planning. I've been using it for years now and
it's one of the things I look forward to every year.
</p>
<p>
I can also tell that reading through my old ones once a year makes me so happy
I wrote them, so I highly recommend writing similar ones, even if just for
yourself. It's so easy to forget what's been going on and having it written
down every year is a good habit.
</p>
<h2 id="previous-years">Previous years</h2>
<p>
<a href="https://hamatti.org/posts/year-in-review-2016">2016</a>,
<a href="https://hamatti.org/posts/year-in-review-2017">2017</a>,
<a href="https://hamatti.org/posts/year-in-review-2018">2018</a>,
<a href="https://hamatti.org/posts/year-in-review-2019">2019</a>,
<a href="https://hamatti.org/posts/year-in-review-2020">2020</a> and
<a href="https://hamatti.org/posts/year-in-review-2021/">2021</a>
</p>
<h2 id="what-i-hoped-for-2022-last-year">What I hoped for 2022 last year?</h2>
<p>
A year ago, I wasn't very hopeful for the future. Two years of pandemic and
burnout had taken its toll, I was on a sick leave and there was no knowledge
of when the situation would ease out.
</p>
<p>
However, I did write down five hopes for the future: starting a new
documentation meetup, playing more board games, traveling to Europe, doing 2nd
season of
<a href="https://hamatti.org/p/e6f25a9c-d317-4c6b-b226-64257968d865/hamatti.org/codebase/">codebase</a>
and writing more. Surprisingly, I kinda did get 3/5 done this year.
</p>
<p>I prefaced those hopes with this:</p>
<blockquote>
But I'll allow myself to dream an unlikely optimistic future today. If not for
anything else, at least for the sake of "if it happens, I'll have a list of
things to do".
</blockquote>
<p>And yeah, 2022 was way better year than 2020 and 2021.</p>
<h2 id="pandemic-started-easing">Pandemic started easing</h2>
<p>
I started 2022 on a burnout sick leave and spent the first few months at home
pondering and wondering. Just before the turn of the year, I was discussing a
new job opportunity for a dream job but had to quit the process due to the
burnout. I did the only possible decision but I still wish I wouldn't have had
to.
</p>
<p>
In March, I returned to work, the pandemic restrictions were lifted and I got
back to organizing events both at work and at communities outside work. It was
a nice spring. I was still tired as heck but managed to organize and host a
ton of great events and helping some local meetups run their first events
since the pandemic pause.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/11/djangocph-dinner.jpg" class="kg-image" alt="Five people around a round table in a restaurant, all looking towards the camera" />
<figcaption>
Me and fellow speakers on a dinner in Copehagen before Django Day
</figcaption>
</figure>
<p>
I also got to travel a bit when I took the boat and the train to Copenhagen
for <a href="https://2022.djangoday.dk/">Django Day 2022</a> where I met
amazing people, made new friends and gave
<a href="https://www.youtube.com/watch?v=b_-OGJLtUQU">a talk about debugging</a>
(and
<a href="https://2022.djangoday.dk/talks/lightning-talks/">a silly lightning talk about version numbers</a>). During that trip I also had a chance to hang out with friends and
colleagues in Futurice's Stockholm office and got a custom Futu-Juhis LEGO
figure made in the LEGO store of Copenhagen.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/11/rustlings-talk.jpg" class="kg-image" alt="Juhis standing in front of audience presenting with a slide that says "Lightning talk ⚡: rustlings"" />
<figcaption>Doing a talk in Rust Finland meetup</figcaption>
</figure>
<p>
I also got to talk in a few other meetups: HelPy, Rust Finland, TurkuSec,
Koodiklinikka and Codete Tech Lead meetup as well as teach programming in a
<a href="https://www.hdl.fi/digista-voimaa-hanke-kehittaa-nuorten-digitaitoja/">program co-organized by Vamos and Futurice</a>.
</p>
<h2 id="searching-for-a-new-job-starting-in-one">
Searching for a new job & starting in one
</h2>
<p>
During 2021 and 2022, I talked with a lot of companies about jobs. During that
process, I interviewed with almost 40 companies and in March, I applied to
<a href="https://hamatti.org/p/e6f25a9c-d317-4c6b-b226-64257968d865/mozilla.org/">Mozilla</a> for
a new open position for a developer advocate. Two long months of interviewing
and waiting led to an offer to start as a Senior Developer Advocate for
Firefox Add-ons in Germany.
</p>
<p>
Since January of 2017, I'd been looking for an opportunity to move to Germany
and finally I had one. In June, I packed up my stuff into a storage container
in Helsinki, headed out to the countryside for a short holiday and in the
beginning of July took the boat and handful of train rides through Stockholm,
Malmö, Copenhagen and Hamburg on route to Berlin to start a new life. Moving
to a new country with public transportation was exciting.
</p>
<p>
My holiday month of July was spent on getting to know my neighborhood, playing
board games 3-4 nights a week with new friends and sweating a lot, as the
heatwave swept over Germany.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/11/afds-mozilla.jpg" class="kg-image" alt="Juhis wearing a red cap, grey hoodie with Mozilla hoodie and a conference bad, standing outside with rooftops of Amsterdam in the background" />
<figcaption>In Ad-Filtering Dev Summit in Amsterdam</figcaption>
</figure>
<p>
In August, I started my new job and spent the second half of the year learning
the ropes of building Firefox extensions, getting to know people in the
organization and the community, and learning the tools and processes.
</p>
<p>
During the fall, I built a few browser extensions, two of them published:
<a href="https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/">Pokemon TCG Card Viewer</a>
and
<a href="https://hamatti.org/posts/new-firefox-extension-to-help-pokemon-tcg-players">Pokemon TCG Online Code Redeem Helper</a>. The others I built as experiments to learn different aspects of extension
building. I wrote a few blog posts and got to teach
<a href="https://hamatti.org/posts/kittens-everywhere-how-to-build-a-browser-extension/">a class on how to build a browser extension</a>
to university students in US.
</p>
<p>
Teaching that class, creating the materials and helping students build
extensions as part of their class was definitely one of the biggest positive
highlights of this year. It was exactly what I love to do.
</p>
<p>
I visited
<a href="https://hamatti.org/posts/ad-filtering-dev-summit-2022-recap/">Ad-Filtering Dev Summit</a>
in Amsterdam and
<a href="https://hamatti.org/posts/devrelcon-prague-2022-recap">DevRelCon</a>
in Prague as well as took a trip to Helsinki to organize
<a href="https://hamatti.org/posts/react-finland-2022-recap/">React Finland</a>.
</p>
<p>
All of those trips were amazing. I got to meet and reconnect with old friends,
make a ton of new ones and especially in Prague, it was so lovely to connect
with colleagues in the industry with whom I'd only interacted online until
now.
</p>
<p>
However, I didn't really get into the meetup and community buzz in Berlin.
Partly because it seems the events haven't quite started yet due to the
pandemic and partly due to working mostly evenings as my closest colleagues
are in US and Canada.
</p>
<p>
That was a bit of a bummer but I did get involved with
<a href="https://codebar.io/berlin">the Berlin chapter of codebar</a> and
coached in a few workshops in-person and virtually.
</p>
<h2 id="writing">Writing</h2>
<p>
During 2022 I ended up writing around 50 blog posts in different blogs, mainly
this one. I had a couple of breaks in the middle: in January/February I was
feeling so down on my sick leave and in August I was so exhausted and busy
with the onboarding at Mozilla.
</p>
<p>
I still consider this a good win for the writing. I was able to maintain a
mostly weekly pace, wrote a good amount of technical content and enjoyed
writing a ton. I still didn't however make any progress on two of my longer
form guides that I've been wanting to get done for a good two years already
now. 2023 maybe?
</p>
<p>
I also wrote some blog-post-style-ish writeups about
<a href="https://hamatti.org/adventofcode/2022/">my Advent of Code solutions this year</a>.
</p>
<p>
In my job, I also got to work with a brilliant copy editor and collaborating
with him is definitely improving my writing skills. It is so great to write
blog posts when I have a technical team to discuss with (and who review) my
content and a copy editor and marketing team who help me with polishing the
content and story.
</p>
<p>Here are my favorites from 2022:</p>
<ul>
<li>
<a href="https://hamatti.org/posts/added-custom-highlight-function-for-nhl-235/">Added custom highlight function for nhl-235</a>
(Feb 2022)
</li>
<li>
<a href="https://hamatti.org/posts/shy-introverts-short-guide-to-speaking-in-conferences/">Shy introvert's (short) guide to speaking in conferences</a>
(Apr 2022)
</li>
<li>
<a href="https://hamatti.org/posts/a-week-in-life-of-a-developer-advocate/">A week in life of a developer advocate</a>
(May 2022)
</li>
<li>
<a href="https://hamatti.org/posts/javascripts-console-is-so-much-more-than-just-console-log/">Javascript's console is so much more than just console.log</a>
(Jun 2022)
</li>
<li>
<a href="https://hamatti.org/posts/developers-guide-to-communities/">Developer's Guide to Communities</a>
(Jul 2022)
</li>
<li>
<a href="https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/">I built a Firefox extension for Pokemon TCG players</a>
(Sep 2022)
</li>
<li>
<a href="https://hamatti.org/posts/kittens-everywhere-how-to-build-a-browser-extension/">kittens-everywhere – how to build a browser extension</a>
(Sep 2022)
</li>
<li>
<a href="https://hamatti.org/posts/python-prep-for-advent-of-code-2022/">Python prep for Advent of Code 2022</a>
(Nov 2022)
</li>
</ul>
<p>
For a good part of the year, I also managed to maintain my
<a href="https://hamatti.org/weeklies/">Weeklies</a>, a weekly curated
collection of interesting articles, videos, games and other content. I plan to
return to it, hopefully early next year.
</p>
<h2 id="communities-events">Communities & events</h2>
<p>
I already wrote a bit about the events. Since I don't have access to my old
work calendar anymore and I didn't take full notes of all the ones, I can't
really estimate how many. On the busiest week in late April, I had 6 events on
a single week and it was so much fun.
</p>
<p>
With <a href="http://turkufrontend.fi/">Turku ❤️ Frontend</a>, we organized 7
meetups and I continued organizing the events in the background after moving
to Berlin as well, helping out with speakers, sponsors, marketing and comms.
It's amazing
<a href="https://medium.com/@turkufrontend/7-years-with-turku-%EF%B8%8F-frontend-536c7f55b1f9">it's been already 7 years since I founded the community</a>. It's my proudest creation in my life and I've gained so many new friends
through it – and a whole new career in developer relations.
</p>
<p>
We also did a bunch of Helsinki Dev Lunches in the spring and I got to invite
quite a few student groups to Futurice's office for excursions in the spring
as well.
</p>
<p>
At Koodiklinikka, I helped run
<a href="https://koodiklinikka.github.io/palkkakysely/2022/">our annual salary survey</a>
with almost 700 participants from our developer community. It was a lot of
fun, I got to make a ton of funny memes and have a bit of different kind of
interactions with the community than I do on a daily basis.
</p>
<p>
It was a weird year when it comes to communities and events. Moving to Berlin
was such a big change that for the second half of the year I lost connection
to most of my old communities without yet forming strong connections with new.
</p>
<p>
I did organize a small Hacktoberfest gathering at Mozilla's Berlin office in
October and used it to work on
<a href="https://hamatti.github.io/nhl-235/">the documentation of 235</a>.
</p>
<p>
And I ate a brunch that contributed in a small part into Matias starting a new
meetup group
<a href="https://aurajoki-overflow.github.io/website/">Aurajoki Overflow</a>,
a very welcomed addition to Turku's meetup scene. And I did a birthday talk
about communities in TurkuSec's 6th birthday. I'm so happy to see both of
those communities in the Turku dev scene!
</p>
<h2 id="software">Software</h2>
<p>
This year I also wrote a bit of code. I actually started the year by migrating
my
<a href="https://hamatti.org/adventofcode/2021/">2021 Advent of Code writing into this website</a>.
</p>
<p>
I continued improving two of my older projects,
<a href="https://github.com/Hamatti/nhl-235">235</a> and
<a href="https://glc-checker.netlify.app/">Gym Leader Challenge Decklist Validator</a>, making small improvements on feature, documentation and presentation side.
</p>
<p>
I built
<a href="https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/">Pokemon TCG Card Viewer</a>
and
<a href="https://hamatti.org/posts/new-firefox-extension-to-help-pokemon-tcg-players">Pokemon TCG Online Code Redeem Helper</a>, two Firefox extensions for Pokemon TCG players. To have an example project
for my
<a href="https://hamatti.org/posts/kittens-everywhere-how-to-build-a-browser-extension/">How to build browser extensions class</a>, I built
<a href="https://hamatti.org/p/e6f25a9c-d317-4c6b-b226-64257968d865/github.com/hamatti/kittens-everywhere-example">kittens-everywhere</a>
to showcase how to get started.
</p>
<p>
A funny story about the kittens everywhere: I mostly built it on a 31-hour
boat ride from Helsinki to Travemünde without access to Internet which made it
quite an experience and a challenge.
</p>
<p>
2021 was a great year for me in Advent of Code, due to having a lot of extra
time on my hands and a need to distract myself from the world. This year,
<a href="https://hamatti.org/adventofcode/2022/">I took my trusted toolkit of Python and Jupyter Notebooks and solved what I
could</a>
and it turned out really great. I'll share more about that in an Advent of
Code retrospective in a few weeks.
</p>
<h2 id="so-tired">So tired</h2>
<p>
I knew that I was pushing hard on the second half of the year but I only
realized how hard when I arrived to Finland for the Christmas holidays. When I
didn't have to push and keep myself together, I crashed hard and spent three
days mostly in a hotel bed being so completely tired. I had a few lunches and
dinners with friends but didn't find energy to do anything else.
</p>
<h2 id="2023-here-we-come">2023, here we come</h2>
<p>
Past few years, this part of the journal has been the hardest but this year
I'm once again excited for the future. There's still a lot of things to figure
out in my personal and professional life so I'll dedicate 2023 to getting back
to basics.
</p>
<p>
I want to focus on my physical and mental health that I've been sacrificing
over the past 5-6 years while chasing success and battling the burden of the
pandemic. I want to get healthier and get a better grasp of the day-to-day as
that's been my main challenge lately.
</p>
<p>
And yeah, I do want to make progress with my writing this year as well.
Writing is one of those things that I really enjoy. Having my own blog that I
finally managed to get into a shape where I write regularly has been something
I'm very proud of it. It's also very handy because I keep sending links to
blog posts regularly when questions come up, so it's also doing its job as a
documentation for future.
</p>
<p>
I'm launching a new newsletter for software developers next week! I'm super
excited about it, keep an eye on this blog and
<a href="https://mastodon.world/@hamatti">my social media</a> so you won't
miss it. (Or take a sneak peek at
<a href="https://www.syntaxerror.tech/">https://www.syntaxerror.tech/</a> and
sign up before others get a chance!) I've written some parts of it already and
even I have to admit, it might be one of the better things I've done.
</p>
<h2 id="a-wrap-up">A wrap up</h2>
<p>
I made many new friends and got to catch up with many existing ones this year.
I got through the worst part of the pandemic and while it's still not over,
things look way better than they have in a long time.
</p>
<p>
Thanks to everyone who was part of my 2022 and made the year fun and the
adventures worth it. I hope I was able to bring some joy, happiness, new ideas
or inspiration or new friends to your lives too.
</p>
<p>
Next week, I'll head back to Berlin and I'm ready for the new adventures. This
time, with optimism.
</p>
Merry Christmas
2022-12-21T00:00:00Z
https://hamatti.org/posts/merry-christmas-2022/
<p>May the holiday season be good for you, my friends ❤️.</p><p>I already started my Christmas holidays this year on the 16th, earlier than usual and traveled home to spend a few weeks with family and friends. I like to take a break from the busy day-to-day life, slow down, play a few days worth of good board games, read a book and reflect on the past year – and plan for the new.</p><p>Next week I'll share my annual Year in Review post for 2022. Spoilers: it was way better than 2020 or 2021.</p>
7 years of Turku ❤️ Frontend (via Medium.com)
2022-12-15T00:00:00Z
https://hamatti.org/posts/external-7-years-of-turku-frontend/
It's been seven years since we started Turku ❤️ Frontend. I went down the memory lane to look back at this wonderful time.
Read full article at <a href="https://medium.com/@turkufrontend/7-years-with-turku-%EF%B8%8F-frontend-536c7f55b1f9">medium.com/@turkufrontend</a>.
DevRelCon Prague 2022 Recap
2022-12-14T00:00:00Z
https://hamatti.org/posts/devrelcon-prague-2022-recap/
<p>I got to spend a lovely week in Prague to finish off the adventurous year of 2022. And it was amazing, both for the trip and for the conference.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/WhatsApp-Image-2022-12-06-at-18.04.05.jpeg" class="kg-image" alt="Juhis on lying a couch on stage with DevRelCon logo on the large screen behind him" /></figure><h2 id="back-in-person">Back in person</h2><p>Last year in DevRelCon Online 2021, I gave <a href="https://developerrelations.com/dev-rel/the-most-social-and-loneliest-job-being-a-solo-developer-advocate">a talk about the juxtaposition of how a solo developer advocate can have the most social and the loneliest job at the same time.</a> At the time, I got a few 👏 emojis in Discord and made a blunder that in my head didn't make the talk the best it could be.</p><p>I can say I was amazed by the warm welcome I got to this year's conference. So many people came to talk to me, giving positive feedback for the last year's talk. It was so cool to see these people for the first time face to face since I did give the talk to my plushies and webcam at home. (I had a similar experience in 2021 when I did a remote lecture at university in March/April and met the students for the first time in November and it felt nice to hear the actual feedback.)</p><p>The talk truly seemed to resonate with a lot of developer advocates who are working alone in their teams.</p><p>But it was also a bit absurd feeling as I've spent all my life kinda building small things at the northern isolated corner of Europe, away from most of the other DevRel world and somehow in this conference people knew me and what I do.</p><p>It was also lovely to catch up with some old friends (and some surprising reunions with people I didn't expect to see in DevRelCon) as well as make a lot of new ones.</p><h2 id="the-traditional-pre-conference-dinner">The traditional pre-conference dinner</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/WhatsApp-Image-2022-12-05-at-22.09.51.jpeg" class="kg-image" alt="Three men sitting in a restaurant, taking a group selfie and smiling" /></figure><p>For me, meeting people and sharing the excitement over what we love to do is the best part of conferences. Since 2019, I've always attempted to get some pre-conference dinners done to meet people. This year was no exception. I invited some people to join in conference Discord and Marcus and Emillien answered the call and we had a lovely long evening discussing all things DevRel.</p><p>I'm still amazed how the super shy me turned out to what I am these days: not only participating in things but actively organizing social gatherings.</p><h2 id="first-day-">First day!</h2><p>The first day of the conference was really good. In addition to catching up with old and new friends and discussing the usual DevRel concepts like neurochemistry, nicknames & personal branding, societal revolution and enjoying life, there were a couple of really good talks I want to highlight that hit home for me.</p><p>The talks are likely to be shared online at some point and at that time, I'll add links to them.</p><h3 id="toying-with-people-s-emotions-a-cognitive-theory-of-devrel-by-don-goodman-wilson">Toying With People’s Emotions: A Cognitive Theory of DevRel by Don Goodman-Wilson</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/devrelcon-2022-don.jpg" class="kg-image" alt="Don standing on stage wearing a white shirt. A slide project next to him says "It's never about you. It's always about them."" /></figure><p>By far the one that had the biggest impact for me was Don's talk at the end of the day. I've been building communities for nearly two decades (20-year anniversary coming up next February) and I've been successful with that not quite understanding <em>why. </em></p><p>Don talked about mainly about oxytocin as the neurotransmitter of love in the context of building communities and so many things clicked in my head. I've always been focusing on things like making community members feel welcome and appreciated; building long-term relationships through repeated social interactions and community participation; and with a lot of empathy, care and kindness.</p><p>It was lovely to have someone say it out loud and in a structured format that helps me put it into words in the future as well.</p><h3 id="beyond-the-kitchen-sink-making-the-case-for-small-demos-by-kevin-lewis">Beyond the kitchen sink: making the case for small demos by Kevin Lewis</h3><p>The talk I was most looking forward to was Kevin's talk about small demos. I'm a huge fan of building small demos that introduce or demonstrate a single concept in a very concise and recipe-like format.</p><p>Kevin demonstrated in the talk brilliantly the different types of demos from simple atomic demos to more complex and fully-featured kitchen sink demos.</p><p>This combined with a great discussion that I had with Marcus on the pre-conference dinner about designing and creating learning paths gave me a lot of inspiration and new ideas on how to approach developer education in this context and how to help developers learn the tools better.</p><h3 id="turning-users-into-advocates-at-scale-by-karin-wolok">Turning Users Into Advocates, at Scale by Karin Wolok</h3><p>The third talk I want to highlight from the first day was Karin's talk about the processes and paths you can build to help your users become more engaged and active in the community with small steps that don't overwhelm them from the beginning.</p><p>An effective way to scale DevRel is through community champions/ambassadors, people who become so active in the community that they help run it and help other members in the community to find help and inspiration through interactions with each other.</p><h2 id="second-day-">Second day!</h2><p>I missed the first block of the second day when I stayed in the hotel to take care of community admin work for other communities but I'm really looking forward to catching up with Brandon West's <em>How effective DevRel helps build great companies </em>when it becomes available.</p><h3 id="building-a-champions-program-from-scratch-tips-lessons-learned-and-more-by-ully-sampaio-seeding-an-ambassador-program-by-rebecca-marshburn">Building a champions program from scratch: tips, lessons learned and more by Ully Sampaio & Seeding an ambassador program by Rebecca Marshburn</h3><p>On the second day, there were two fantastic talks about building ambassador/champion programs. It's always been one of my favorite methods in community building, no matter if I've been the one building or being an ambassador.</p><p>Ully shared her work building Elastic's champions program from scratch, starting in Brazil and expanding to global reach and how their program selects the champions and rewards them for their contributions.</p><p>Rebecca focused on the first steps of starting a program and planting the seeds. She explained with very practical examples how they started their ambassador program at Common Room.</p><p>My first experience with ambassador programs was when I was <a href="https://buffer.com/resources/community-leaders/">an ambassador for Buffer in their Community Leaders program</a>. After that experience, I was sold with the idea. Arielle shared some great tips in that blog post for anyone looking to start one so read that while you wait for these videos to arrive.</p><h3 id="lightning-talks-by-marc-duiker-eti-noked-and-olena-kutsenko">Lightning talks by Marc Duiker, Eti Noked and Olena Kutsenko</h3><p>In the lightning talks block we saw three very different topics by three amazing speakers. </p><p>Marc opened the session by sharing how he's been using his pixel art hobby to complement his community work and how one can build their personal brand and build an audience through their non-technical creative work. Eti talked about the work they've done at Wilco to empower developers. Olena brought in the science to help fellow speakers calm their nerves short and long term before getting on the stage to speak.</p><h3 id="one-devrel-hat-and-500-devs-to-wear-it-by-alena-osipova">One DevRel hat and 500 devs to wear it by Alena Osipova</h3><p>Alena's talk about how she and her team are building internal community with Kiwi's engineers and empowering their developers to participate in the community resonated very much with me as there was so much in common with the work I did in my previous job at Futurice.</p><p>Building a developer community culture internally at the company is not an easy feat but I do think it's an undervalued and underused opportunity for many companies. Bringing people to share their expertise in the community through talks, blog posts, articles, workshops and community participation is such a powerful tool in the brand awareness, employee retention and talent acquisition and growth of the engineers.</p><h2 id="prague-is-beautiful-as-always">Prague is beautiful as always</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/prague-opera-house.jpg" class="kg-image" alt="Prague's state opera house in the evening, lit up with lights. Czech and Ukrainian flags hanging on the outside." /></figure><p>I'm always happy when I get to visit Prague. It's such a lovely city with many friends, good beer and food and so much to see and experience.</p><p>When traveling to Prague, I also always get to experience my favorite train ride in the world, the leg between Děčín, Czechia and Bad Schandau, Germany. The tracks go alongside a river and on the other side of the river there's hills and houses on the hill side. Every time I'm glued to the window between those two cities and I just admire the beauty of it all.</p><p>I also had some time to catch up with old friends from previous Czechia trips and a day to admire the city.</p><h3 id="quick-recommendations">Quick recommendations</h3><p>If not for anything else, this is for my next Prague trip:</p><ul><li><a href="https://hongkongkarlin.cz/">Hong Kong Karlin</a>, a great lunch place with Asian fusion kitchen</li><li><a href="http://www.peterspub.cz/">Pete's (Burger) Pub</a>, good burgers and tasty local beer; also nice Czech lunch options</li><li><a href="https://www.meatbeer.cz/">Meat Beer</a>, more burgers and beer, right next to the railway station on the north-west corner</li><li><a href="https://www.nejenbistro.cz/">Nejen Bistro</a>, we had a great pre-conference dinner there and the supreme of chicken was delicious</li><li><a href="https://www.porke.cz/">Porke</a>, a Mexican-Czech tapas restaurant where Honza took me for lunch. Really good food and lovely staff.</li><li><a href="https://www.kafeatrium.cz/">Kafe Atrium</a>, where we spent an afternoon working, brainstorming and making the world a better place. </li></ul><h2 id="thank-you-all-">Thank you all!</h2><p>Big thanks to Matthew and the team for organizing another stellar DevRelCon. Big thanks also to all the speakers for sharing and inspiring. And the biggest thanks for all the old and new friends I got to meet during these 4 days in Prague and have brilliant discussions. This was definitely a top highlight of my 2022.</p><h2 id="other-people-s-experiences">Other people's experiences</h2><p>I wasn't the only one at the event and not the only one who shared their experience in the blogosphere (btw, blogosphere > metaverse any day).</p><ul><li><a href="https://dev.to/floord/devrelcon-2022-a-very-biased-review-1n73">Floor Drees: DevRelCon 2022 - a very biased review</a> </li><li><a href="https://www.jaminologist.com/my-devrelcon-prague-2022-a-quick-recap/">Benjamin Bryant: My DevRelCon Prague 2022 - A Quick Recap</a></li><li><a href="https://developerrelations.com/events/devrelcon-prague-2022-retrospective">Matthew Revell: DevRelCon Prague 2022 retrospective</a></li><li><a href="https://dev.to/sebastienblanc/its-rough-out-there--3gfo">Sebastien Blanc: It's rough out there ... </a></li><li><a href="https://aiven.io/blog/devrelcon-prague-2022-recap">Marion Nehring: A sense of DevRelCon</a></li></ul><p><em>If you wrote about the event and are not on the list above, ping me in <a href="https://mastodon.world/@hamatti">Mastodon</a>, I'd be happy to add it to the list!</em></p>
Verifying identity online
2022-12-07T00:00:00Z
https://hamatti.org/posts/verifying-identity-online/
<p>Last month, one of the hot topics was <a href="https://www.theverge.com/2022/11/9/23450289/twitter-impersonators-official-mario-musk-jesus-valve">the mess</a> that was created in Twitter regarding their <a href="https://www.theverge.com/2022/11/8/23448184/twitter-verification-official-checkmark-gray-blue">new Verified badge</a>. I want to explore a bit what happened, what other options are available and why verification is such a challenging issue.</p><h2 id="the-twitter-model-s-">The Twitter model(s)</h2><p>Before the mess of this month, Twitter had a verification model where certain people who <a href="https://help.twitter.com/en/managing-your-account/legacy-verification-policy">matched criteria</a> would get a blue "Verified" icon next to their name. The idea was to offer the users a way to know which of the popular, well-known or official accounts were legit and which were not.</p><p>This system wasn't without its flaws. There was a lot discussion every now and then about the criteria and the bias in the system. It seemed unclear to many what the real criteria were and if everyone was treated fairly.</p><p>And to many, the blue verification icon became a status symbol. When Musk took over the company and allowed anyone to buy a blue verified icon next to their name for $8/month, chaos ensued. People impersonated other people and organizations and there was no good way for users to know one from the other.</p><p>Even with the flaws of the original system, it did provide value, mainly through its lack of false positives. I haven't heard of cases where in the old system, someone was verified to be a person they weren't or organization they didn't represent.</p><p>The challenge is that if anyone can "verify" by paying (and not being checked if they are who they claim to be), the verification is completely pointless as we learned from the Twitter case. The problem with a real verification though is too that sending your ID to a company might not be at your best interest either.</p><h2 id="the-mastodon-model">The Mastodon model</h2><p>Mastodon seems to be the place where a lot of Twitter users are heading towards – <a href="https://hamatti.org/posts/i-joined-mastodon/">myself included</a>. There's a way <a href="https://docs.joinmastodon.org/user/profile/#verification">to "verify" yourself</a> to be who you claim to be – kinda.</p><p>In Mastodon, there's no authority to provide verification badges. There's no one to send your ID to ask for verification. The system works in a bit different way. You can verify a connection between two things online: for example, your website.</p><p>By listing your website in your profile and adding a <code><link rel="me" href="[your mastodon URL"></code> to that same site, Mastodon will show a check mark next to it, verifying that the person who manages the Mastodon profile also manages or is able to edit the website they link to.</p><p>That's why in <a href="https://mastodon.world/@hamatti">my Mastodon profile</a>, this website is listed as green with a checkmark.</p><p>The important distinction is this: <strong>it does not prove that the Mastodon profile is managed by the real person Juha-Matti Santala.</strong> But if you trust this website, you can at least be somewhat sure that the profile belongs to me. Sure, there's a possibility of hacking a website to add that back link or buying an expired domain but it's still a nice way.</p><p>It's not always important that we are able to verify that Mastodon profile <a href="https://mastodon.world/@hamatti">https://mastodon.world/@hamatti</a> is <strong>me, the person.</strong> For me, it's actually more valuable to verify that it is maintained by the same online person who is writing this website. For example, you could be an anonymous writer or comic book author and want to make sure your website links to your authentic profile. In that case, the real person identity is not important nor valuable.</p><h3 id="it-s-a-very-techie-solution">It's a very techie solution</h3><p>To a techie, this model sounds amazing. Until we remember that not a lot of people actually have websites, meaning there's no way for them to verify their identity in Mastodon. There are some ways though: for example, a media company or a public government organization might have profile pages in their website that handle this verification. But for a non-technical individual who's not part of an organization or doesn't want to tie their profile to an organization, it doesn't provide much.</p><p>A big benefit of this model is that you don't need to send your ID to a commercial party. You can be in charge of what and how you want to verify your identity. The challenge is that you can only ever trust that the person with the profile has access to the website they link to.</p><h3 id="don-t-trust-it-blindly">Don't trust it blindly</h3><p>There are ways to spoof an identity using this model. There are websites that use small differences with what is called <a href="https://en.wikipedia.org/wiki/IDN_homograph_attack">IDN homograph attack</a>. You could buy a domain to a website that looks at a glance like a real organizations website and verify through that. A regular user who expects no malice could be fooled by it easily.</p><p>So before you believe a check mark in Mastodon, follow through the website and confirm it is what it claims to be.</p><h2 id="gpg-gnu-privacy-guard">GPG - GNU Privacy Guard</h2><p>Another way to verify (<em>to sign</em>) your digital messages is using GPG keys. I'm not an expert in cryptographic signing though so if you want to get a good primer I recommend checking out <a href="https://paul.fawkesley.com/gpg-for-humans-why-care-about-cryptography/">Paul Fawkesley's GPG For Humans blog series</a>.</p><p>GPG works with two keys: your public and your private/secret key. When it comes to signing messages to confirm that they come from you, you sign them with your secret key and you give your public key to whoever you want to be able to verify the messages come from you.</p><p>The verification method here is similar to the one in Mastodon in that you still need another way to build that original trust. It works well for messaging with people you already know.</p><h2 id="conclusion">Conclusion</h2><p>Verifying identity is always tricky because regardless of the method, you need to have a trust baseline: something that you trust in regardless of the methods. The government offers one way to identify through an ID (personal ID, driving license, passport, etc) and we've been relying on that for a lot of things.</p><p>When it comes to verifying your identity online though, you either </p><p>a) need to provide a proof of your identity to a company that then tells their users that the person is who they claim to be, or </p><p>b) you need to use a different kind of system that doesn't actually verify your identity, just that you have access to something that the users trust to be yours (e.g. a website or private key).</p><p>To some extent, I'm happier with the second option but I also do recognize that as a technical person, I'm at a very privileged situation offering me more ways to deal with that.</p>
Mastodon redirect with your domain in Netlify
2022-12-07T00:00:00Z
https://hamatti.org/posts/mastodon-redirect-with-your-domain/
<p>Because Mastodon is <a href="https://www.eff.org/deeplinks/2011/03/introduction-distributed-social-network">federated</a>, the account name includes both the name and the server. For example, my account is in mastodon.world, so you can find me as @hamatti@mastodon.world compared to just @hamatti as it would in a single-system social network.</p><p>It does have one downside though: there are so many servers and many named quite similarly that it can be easy to forget someone's handle. For example, there's <a href="https://mastodon.world/">mastodon.world</a> and <a href="https://mastodon.social/">mastodon.social </a>or in the Finnish network, there's <a href="https://mastodontti.fi/">mastodontti.fi</a> and <a href="https://mastodo.fi/">mastodo.fi</a>.</p><p>You could setup your own server on your own domain but managing a server is not always a good option and you might not want the maintenance burden or potential legal considerations to your shoulders just to have an easier-to-remember handle.</p><p>I'm using Netlify to host this website, <a href="https://hamatti.org/">hamatti.org</a>, and I made some magic happen and now if you copy @hamatti@hamatti.org into your Mastodon instance search window, it will find me from @hamatti@mastodon.world:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/12/Screenshot-2022-12-07-at-21.51.49.png" class="kg-image" alt="Mastodon search screen with search query @hamatti@hamatti.org returning account for @hamatti" /></figure><p>Not only is it easier to remember but it also looks hella cool and you earn that sweet geek street cred when you tell your handle to a friend in a conference.</p><h2 id="how-to-do-it">How to do it?</h2><p>Netlify has support for a <code>_redirects</code> file where you can define redirect rules. To make the Mastodon thing work, you only need to add this one rule:</p><pre><code class="language-bash">/.well-known/webfinger/* https://{your_mastodon_domain}/.well-known/ webfinger?resource=acct:{your_account}@{your_mastodon_domain} 301</code></pre><p>If you're using (or anticipating to use) the <code>./well-known/webfinger/</code> for something else too, you probably want to be more specific but I don't know what it's used for so I'll cross that bridge if I get there.</p><p>Add that line to the file, replace the curly-braced things with your details, deploy to Netlify and boom, you're cool.</p><h2 id="what-if-i-m-not-using-netlify">What if I'm not using Netlify?</h2><p>The redirect rule is still the same but you need to figure out how to do it with your service provider or server software. Just google "[your provider] redirect rules".</p><h2 id="be-careful-of-what-you-redirect">Be careful of what you redirect</h2><p>Jan 25th update: I learned today from <a href="https://mastodon.nz/@braedon/109747332533789520">this toot</a>:</p><blockquote>PSA: If you're setting up a Mastodon account alias on your own domain, <strong>don't</strong> add a <code>/.well-known/nodeinfo</code> resource.<br />A NodeInfo that points/redirects to another Mastodon server (e.g. mastodon.social) says that your domain is a full Mastodon server, with all the details of mastodon.social - e.g. its user counts. That's bad!<br />There's no need to have a NodeInfo resource on your domain to set up an alias - you only need the WebFinger resource.</blockquote>
Python prep for Advent of Code 2022
2022-11-30T00:00:00Z
https://hamatti.org/posts/python-prep-for-advent-of-code-2022/
<p>We are 1 day away from this year's <a href="https://adventofcode.com/">Advent of Code</a>, the annual software development puzzle solving Christmas calendar. <a href="https://hamatti.org/adventofcode/2021/">Last year</a>, I solved nearly all puzzles and did so with Python and Jupyter Notebooks in my toolbox. I've also spent some time since that working on previous years' puzzles with the same setup.</p><p>This year, you can follow my progress and writings in GitHub: <a href="https://github.com/Hamatti/adventofcode-2022">https://github.com/Hamatti/adventofcode-2022</a></p><p>In this blog post, I want to share some of the approaches and especially standard library modules and functions that I've shared in the past as part of my solutions. Getting familiar with these not only helps you solve Advent of Code solutions but also expands your real-life Python toolkit.</p><h2 id="building-your-own-utility-library">Building your own utility library</h2><p>My first tip is to build a utility library to help with the things you end up doing every day. Mine usually starts with one function and then I might add some throughout December if I need some more special cases:</p><pre><code class="language-python">import os
def read_input(day, transformer=str, example=False):
"""
Given a day number (1-25), reads the corresponding input file into
a list with each line as an item.
Runs transformer function on each item.
"""
try:
if example:
filename = f'day_{day}_example.txt'
else:
filename = f'day_{day}.txt'
with open(os.path.join('..', 'inputs', filename)) as input_file:
return [transformer(line.strip()) for line in input_file]
except FileNotFoundError as e:
print(e)</code></pre><p>My first function is <code>read_input</code> and it's a helper that reads data from <code>inputs/</code> folder. It takes three arguments: <code>day</code> is an integer to tell which day's input we want to read, <code>transformer</code> is a function (defaulting to <code>str</code>) that is run on each line of the input. <code>example</code> enables me to store the provided examples as input files and run the same code on both the example and the actual input.</p><p>Transformer function is an interesting and very helpful one. I want to keep my puzzle solution code as focused on the puzzle as possible and not parsing the input. So I can create a function that gets passed into the input reader. If the values are numbers, I can pass <code>int</code>. If the input is list of comma separated items, I can pass a function that splits on comma. And so on.</p><p>Since these actions are things you do every single day with every puzzle, writing a function for it is very helpful.</p><p>My utility library is these days only about reading inputs but there's a lot of opportunities to build your own in a way that helps you the best. A good example for inspiration is <a href="https://github.com/vilhok/aoc-lib">Vilho's aoc-lib for Java</a> that manages boilerplate creation, input file downloading, caching, benchmarking and other features.</p><p>This fall I started <a href="https://github.com/Hamatti/advent-of-code-extension">experimenting with a Firefox extension</a> that when clicked, would copy that day's puzzle input to my clipboard to save me from a few clicks whenever I'd need that data. I haven't published it yet for Firefox though as I'm</p><h2 id="zip-zip_longest">zip & zip_longest</h2><p>For Advent of Code puzzles, <code>zip</code> is such a lifesaver. It combines (zips) multiple iterators into a single one in a way where the individual items from each iterable are paired/grouped together:</p><pre><code class="language-python">dogs = ['Frank', 'Lassie', 'Milo', 'Dug']
movies = ['Men in Black', 'Lassie', 'The Mask', 'Up']
for dog, movie in zip(dogs, movies):
print(f'{dog} is from {movie}')
# Frank is from Men in Black
# Lassie is from Lassie
# Milo is from The Mask
# Dug is from Up</code></pre><p>If the iterables you give to <code>zip</code> are not the same length, <code>zip</code> will just ignore the extras:</p><pre><code class="language-python">numbers = [1, 2, 3, 4]
letters = ['a', 'b', 'c']
for number, letter in zip(numbers, letters):
print(number, letter)
# 1 a
# 2 b
# 3 c</code></pre><p>In cases where you are expecting the original iterables to be of same length, you can add a safeguard with <code>strict=True</code> parameter to <code>zip</code> as that will raise a <code>ValueError</code> if the arguments are of different length. The output will be the same as above for equal length arguments. <em><a href="https://peps.python.org/pep-0618/">This requires Python 3.10</a>.</em></p><p>If you need all of the items to be included, you can use <code><a href="https://docs.python.org/3/library/itertools.html#itertools.zip_longest">itertools.zip_longest</a></code>:</p><pre><code class="language-python">from itertools import zip_longest
numbers = [1, 2, 3, 4]
letters = ['a', 'b', 'c']
for number, letter in zip_longest(numbers, letters):
print(number, letter)
# 1 a
# 2 b
# 3 c
# 4 None</code></pre><p>You can define a <code>fillvalue</code> if you want the missing pairs to be filled a different default than <code>None</code>:</p><pre><code class="language-python">from itertools import zip_longest
items = ['apple', 'banana', 'pear', 'mango']
counts = [5, 12, 3]
for item, count in zip_longest(items, counts, fillvalue=0):
print(item, count)
# apple 5
# banana 12
# pear 3
# mango 0</code></pre><p><em>It's important to note that both <code>zip</code> and <code>zip_longest</code> return an iterator and if you try to print it as-is, you see something like <code><zip object at 0x100fbb440></code>. You can iterate over it (for example in <code>for</code> loops) or pass it to any function that accepts iterables but if you need to turn it into a list specifically, you need to wrap it inside <code>list(your_zip)</code> call.</em></p><h2 id="collections-namedtuple">collections.namedtuple</h2><p><a href="https://docs.python.org/3/library/collections.html">Collections module</a> contains some really nice utilities for puzzles.</p><p>I can't emphasize enough how much I like <a href="https://docs.python.org/3/library/collections.html#collections.namedtuple">namedtuple</a>. Giving meaningful names to your data is so crucial and namedtuple offers a few nice additions on top of just giving your data types aliases.</p><p>Let's take a look an example</p><pre><code class="language-python">from collections import namedtuple
Instruction = namedtuple('Instruction', ['direction', 'distance'])
instructions = []
raw_data = ['R,15', 'L,20', 'U,17', 'L,42', 'D,3']
for datum in raw_data:
dir, dist = datum.split(',')
instructions.append(Instruction(dir, int(dist)))
print(instructions)
# [Instruction(direction='R', distance=15), Instruction(direction='L', distance=20), Instruction(direction='U', distance=17), Instruction(direction='L', distance=42), Instruction(direction='D', distance=3)]</code></pre><p>Here, we have raw data in a form that we could expect for a Advent of Code puzzle: a series of instructions with a direction and distance. We could store this data in regular lists or tuples but with them, 1) we'd have to refer to individual items with numeric indices, 2) it's easy to make mistakes and 3) the output for debugging printing is less useful.</p><p>With namedtuples, we get a nice formatted output (<code>Instruction(direction='R', distance=15)</code> compared to regular tuple's (<code>('R', 15)</code>) and we can refer to individual attributes with <code>instruction.direction</code> instead of <code>instruction[0]</code>.</p><p>I tend to use them a lot in my code for added clarity.</p><p>namedtuples are also "backwards" compatible with regular tuples: you can use them in-place anywhere that accepts tuples. It supports indexing with numbers and unpacks like regular tuples.</p><h3 id="collections-counter">collections.Counter</h3><p>Another handy tool from <code>collections</code> is <a href="https://docs.python.org/3/library/collections.html#collections.Counter">Counter</a>. It is a subclass of dict that takes an iterable as an argument and counts the individual values in that iterable and stores that count data as a dictionary.</p><pre><code class="language-python">from collections import Counter
good_or_bad = [
'good', 'good', 'good',
'bad', 'bad', 'good',
'bad', 'good', 'bad'
]
counts = Counter(good_or_bad)
print(counts)
# Counter({'good': 5, 'bad': 4})</code></pre><p>It has a few added methods that regular dictionaries don't have. One good example is <code><a href="https://docs.python.org/3/library/collections.html#collections.Counter.most_common">most_common</a></code>. Continuing from the previous example:</p><pre><code>print(counts.most_common(1))
# [('good', 5)]</code></pre><p>The method takes an argument <code>n</code> and returns a list of tuples for <code>n</code> most common items. It's useful when you have a lot of data and you just want to inspect for example the top 3.</p><h2 id="collections-deque">collections.deque</h2><p>Last year's Advent of Code was probably the first time I ever used <a href="https://docs.python.org/3/library/collections.html#collections.deque">deque</a>. It's a last-in, first-out (LIFO) data structure "by default" (see below the first code example) with added features. You can <code>append</code> data to the deque and when you <code>pop</code> from it, it removes the latest added item from the deque and returns it.</p><p>In <a href="https://hamatti.org/adventofcode/2021/day_10/">Day 10, 2021</a> I used it to check if nested, matching pairs of parentheses, brackets and braces were in correct order:</p><pre><code class="language-python">from collections import deque
def check_errors(navigation):
pairs = {
'(': ')',
'[': ']',
'{': '}',
'<': '>'
}
openers = deque()
errors = []
for line in navigation:
for char in line:
if char in pairs.keys():
openers.append(char)
else:
opener = openers.pop()
if char != pairs[opener]:
errors.append(char)
break
return errors</code></pre><p>Python's deque (short for double-ended queue) can also append or pop to the left side of the queue, making it possible to use it as a LIFO, FIFO (first-in, first-out), FILO (first-in, last-out)</p><h2 id="itertools-pairwise">itertools.pairwise</h2><p>A relatively recent addition, entering the Python standard library in 3.10 last year, <a href="https://docs.python.org/3/library/itertools.html#itertools.pairwise">pairwise</a> is another great addition to puzzle solving.</p><p>It takes an iterable and creates pairs of consecutive items:</p><pre><code class="language-python">from itertools import pairwise
letters = ['A', 'B', 'C', 'D', 'F', 'G']
for first, second in pairwise(letters):
print(first, second)
# A B
# B C
# C D
# D F
# F G</code></pre><p>An example of this in the context of Advent of Code, last year I used it <a href="https://hamatti.org/adventofcode/2021/day_14/">day 14, 2021</a>. If you are not using Python 3.10 or newer, you can build your own pairwise functionality, for example by using the method I used in <a href="https://hamatti.org/adventofcode/2021/day_1/">day 1, 2021</a> to create sliding windows.</p><h2 id="itertools-product">itertools.product</h2><p>Let's say you need a set of coordinates for x & y, for both being between 0 and 10. One way you could do that is with a cartesian product using <a href="https://docs.python.org/3/library/itertools.html#itertools.product">itertools.product</a>:</p><pre><code class="language-python">from itertools import product
xs = range(10)
ys = range(10)
coordinates = product(xs, ys)
print(list(coordinates))
## Prints
[
(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9),
(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9),
(2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9),
(3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9),
(4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9),
(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8), (5, 9),
(6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 8), (6, 9),
(7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 7), (7, 8), (7, 9),
(8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (8, 5), (8, 6), (8, 7), (8, 8), (8, 9),
(9, 0), (9, 1), (9, 2), (9, 3), (9, 4), (9, 5), (9, 6), (9, 7), (9, 8), (9, 9)
]</code></pre><p>Many Advent of Code puzzles are happening on some sort of coordinate system. My experience and techniques for solving those has changed and evolved a lot over time as I've realized easier and more performant ways to solve them.</p><p>For example, these days I often store data in a dictionary for only those coordinates that have <a href="https://www.freecodecamp.org/news/truthy-and-falsy-values-in-python/">a truthy value</a> and dealing with the full grid of coordinates via looping only when needed (often in debugging prints) and direct math on coordinates on other cases (like determining effect on neighboring cells).</p>
steamos-readonly
2022-11-25T00:00:00Z
https://hamatti.org/posts/steamos-readonly/
<p>The Steam Deck comes configured in a way that part of the filesystem is set to <code>readonly</code> mode. This sometimes gets in the way when installing custom things on the desktop mode. Like when installing the great <a href="https://starship.rs/">Starship prompt</a>.</p><p>You can disable the readonly mode with:</p><pre><code class="language-bash">sudo steamos-readonly disable</code></pre><p>and re-enable it with:</p><pre><code class="language-bash">sudo steamos-readonly enable</code></pre><p>It's not very well documented what parts it actually protects and this command is not very much documented anywhere either.</p>
New Firefox extension to help Pokemon TCG players
2022-11-23T00:00:00Z
https://hamatti.org/posts/new-firefox-extension-to-help-pokemon-tcg-players/
<p>My explorations in the intersection of Pokemon TCG and technology continue as this week I published a new Firefox extension. You can install it from <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-online-code-helper/">addons.mozilla.org</a>.</p><p>Let's set up the scene: a new Pokemon TCG set arrives and you've ordered 100 booster codes for the new set from your favorite game store. Often, those sites send them to you via email in a plain-text list like this:</p><blockquote>Here are your codes:<br />NOT-REAL-CODE-123<br />NOT-REAL-CODE-456<br />NOT-REAL-CODE-789<br />... [repeat 100-200 times]</blockquote><p>The way you'd then redeem these codes is by going to Pokemon's website or opening up the Pokemon TCG Online client and copy-pasting them one by one, redeeming a set every 10 codes. If you miss a character or accidentally got whitespace included in your copy, the code will fail. And it's so easy to lose track of what was the last code you copied successfully if you lose track.</p><h2 id="the-extension">The extension</h2><p>To help with that, I built a Firefox extension that uses Firefox's Sidebar feature and adds a layer of UI around the codes to solve most of the issues above (still can't fix the "1 at the time, redeem every 10 codes" thing though – although when writing this blog post, I got some ideas).</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/11/context-menu.png" class="kg-image" /></figure><p>The way this extension works is simple: you select a chunk of text, any text, and right click to open the context menu and select <strong>Find Pokemon TCG redeem codes</strong> from the menu. The extension will then find all codes that match the format (13 characters from the set of 0-9, A-Z, optionally separated by dashes).</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/11/sidebar.png" class="kg-image" /></figure><p>The extension then loads the codes into a Sidebar, and creates a UI with the code and a <strong>Copy</strong> button. When you click on Copy, it'll copy that code (without mistakes or extra whitespace) to your clipboard and mark the code as used by coloring it green. That helps you see which ones you've already copied.</p><p>Once you're done, you can hit <strong>Clear</strong> to remove the codes from the table and enjoy your cards.</p><p>If you're a fellow Pokemon TCG player, install this extension from the Firefox add-on store AMO and check out <a href="https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/">my other TCG extension as well</a>. I've also built <a href="https://hamatti.org/toolkit/">a few other tools and listed my favorites from other creators on this website</a>. </p><h2 id="a-look-under-the-hood">A look under the hood</h2><p>For those who want to learn and see how it was built, I'll share a few insights here. The entire code is available <a href="https://github.com/Hamatti/ptcgo-redeem-code-helper">in GitHub with an MIT license</a>.</p><p>This project uses three main features for extensions in Firefox: context menu, sidebar and storage.</p><h3 id="context-menu">Context menu</h3><p>To use the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus">context menu</a> (the thing that pops up when you right click something in the browser), you'll first need to declare <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions">the permission for that in the <code>manifest.json</code></a>. In my background script, I then create a new menu item for this:</p><pre><code class="language-javascript">browser.menus.create({
id: "find-codes",
title: "Find Pokemon TCG redeem codes",
contexts: ["selection"],
});
browser.menus.onClicked.addListener((info, tab) => {
switch (info.menuItemId) {
case "find-codes":
browser.sidebarAction.open().then(() => {
setTimeout(() => {
browser.runtime.sendMessage({
action: "addCode",
codes: processCodes(info.selectionText),
});
}, 300);
});
break;
}
});</code></pre><p>Inside the <code>create</code> function, I define <code>contexts: ["selection"]</code> so that the menu item is only available when some text is selected. When the item is clicked, it opens the sidebar, process the selection to extract the codes with <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">regular expressions</a> and sends them to the sidebar script. Since it takes a moment for the sidebar script to run before it's ready to listen for messages, I had to add a short timeout.</p><p>I'm still not sure what the proper or best way to deal with the delay is. Timeouts are not super robust but it works for now.</p><h3 id="sidebar">Sidebar</h3><p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars">Sidebar UI</a> is an HTML page with its own CSS and Javascript that is rendered to a separate sidebar section in Firefox. I have there a bit of instruction text and a table that gets filled with the content based on the codes selected.</p><p>The sidebar script does a few things: it listens to the message from menu, loads and stores the code info from/to the extension storage and renders the content.</p><pre><code class="language-javascript">browser.runtime.onMessage.addListener((message) => {
if (message.action === "addCode") {
processCodes(message.codes);
}
});</code></pre><p>The event listener listens to any messages and if it receives one for <code>"addCodes"</code>, it'll process them. </p><pre><code class="language-javascript">function processCodes(codes) {
const processedCodes = codes.reduce((acc, cur) => {
return { ...acc, [cur]: false };
}, {});
storeToStorage(processedCodes);
render(processedCodes);
} </code></pre><p>Processing codes means three things: 1) turning them from a string array (like <code>["NOT-REAL-CODE-123", "DEF-NOT-REAL-345"]</code>) into an object with the string as key and a <code>false</code> as the value. This keeps track of which codes have already been redeemed, 2) adds them to the storage and 3) renders the sidebar pane.</p><p>When rendering, it will create a table row (<code><tr></code>) for each code, put the code itself into one cell and a button to the other. The button then gets an event listener for the <code>click</code> event.</p><pre><code class="language-javascript">function onClickCopy(ev) {
const target = ev.target;
const parent = target.parentElement.parentElement;
const code = parent.querySelector("td");
const codeToCopy = code.textContent.trim();
code.classList.add("copied");
target.parentElement.classList.add("copied");
navigator.clipboard.writeText(codeToCopy).then(() => {
browser.storage.local.get("ptcgo-redeem-codes").then((results) => {
let codes = results["ptcgo-redeem-codes"];
codes = {
...codes,
[codeToCopy]: true,
};
browser.storage.local.set({
"ptcgo-redeem-codes": codes,
}).then(() => { console.log('Successfully stored in onCLickCopy')}).catch(err => {
console.log(err)
});
});
});
}</code></pre><p>The click listener does a few things itself. The main functionality is to copy the code to the clipboard but it also adds a CSS class to the <code><td></code> to denote its been copied and updates the store with this individual code being marked <code>true</code>.</p><h3 id="storage">Storage</h3><p>A sidebar UI renders from scratch every time it's opened. This means that if we want to provide any kind of persistence, we need to store our data somewhere. To do that, we use the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage">Storage API</a>.</p><p>I update the store on two occasions: 1) when adding code(s) into the sidebar and 2) when copying an element to clipboard.</p><pre><code class="language-javascript">function storeToStorage(codes) {
browser.storage.local.get("ptcgo-redeem-codes").then((fromStorage) => {
const combinedCodes = {
...codes,
...fromStorage['ptcgo-redeem-codes'],
};
browser.storage.local.set({
"ptcgo-redeem-codes": combinedCodes,
}).then(() => { console.log('Successfully stored in storeToStorage')}).catch(err => {
console.log(err)
});
});
}</code></pre><p>To store the codes, I first load the existing ones from the storage, combine them with the new ones and then storing them back. I decided to default existing state in store (<code>{...codes, ...fromStorage['ptcgo-redeem-codes']}</code>) so that an existing code marked as copied won't get overriden.</p><p>The case of marking something as copied was already in the earlier example above.</p><p>Using this storage means the user can close the sidebar if needed (or by accident) and they won't lose their progress.</p><h2 id="original-idea">Original idea</h2><p>To give credit where credit is due, I saw a similar concept done in the <a href="https://www.ptcgostore.com/">PTCGOStore.com</a>, built in to their redeem section. It was so good I decided to move my patronage to them. However, sometimes they don't have all the codes or they may be out of stock so I'll still need to occasionally buy from other places. This blog post is not in any way endorsed nor sponsored by them.</p>
My overengineered tooling for Pokemon TCG Card Viewer extension
2022-11-16T00:00:00Z
https://hamatti.org/posts/my-overengineered-tooling-for-pokemon-tcg-card-viewer-extension/
<p>Hobby projects are fun because you can experiment with things: choosing different languages, frameworks/libraries and approaches than what might be appropriate for commercial production-grade projects.</p><p>In September, I built and released the first version of <a href="https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/">Pokemon TCG Card Viewer extension for Firefox</a> (install from <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/">addons.mozilla.org</a>). Last week, while preparing for the newest expansion, Silver Tempest, to be added to the API I use, I continued tinkering with my development tooling and workflow.</p><h2 id="web-ext-streamlines-my-development">web-ext streamlines my development</h2><p><a href="https://github.com/mozilla/web-ext">web-ext</a> is a command-line tool that makes a lot of extension development easier. It includes a linter to check that the manifest and certain aspects of the code are OK, the default <code>run</code> command opens a fresh Firefox window, loads the extension and provides auto hot reload.</p><p><a href="https://extensionworkshop.com/documentation/develop/web-ext-command-reference/">With flags</a>, there's so much more too! Some that I really like are <code>--browser-console</code> (or <code>--bc</code>) to open up a log and error output console at start, <code>--start-url</code> to define where I want the browser to open up in (handy when the extension only works on specific sites or requires specific type of content to be present) and <code>--target</code> for cross-browser development.</p><h2 id="using-make-for-the-first-time">Using make for the first time</h2><p>This project was the first time for me to use <a href="https://www.gnu.org/software/make/">make</a> for wrapping different operations to be used with easier to remember commands. I chose it as I wanted to learn how to use it but also because this project was a collection of different types of tooling and not a unified project.</p><h3 id="make-build">make build</h3><p>My first make rule was to build the extension:</p><pre><code class="language-bash">build:
zip -r dist/pokemon-tcg-extension.xpi ./* -x makefile .gitignore tests/\* dist/\* docs/\*</code></pre><p><a href="https://extensionworkshop.com/documentation/publish/package-your-extension/">Firefox extension is a zip file</a> of the scripts and configuration that is used for its functions. Remembering the right syntax for <code>zip</code> and trusting that I'd remember to exclude the things that belong to development only were both too difficult, so I decided to wrap them into a simpler <code>make build</code> command. It ignores things like tests, documentation and final product, only packaging what is needed.</p><h3 id="make-test">make test</h3><p>Next, I had a small library that I've developed to tackle certain special cases in the differences when converting Pokemon TCG Online exports to the API's numbering system. For that, I wanted to add some unit tests to be more confident in it.</p><p>To do that, I created a sub project with npm under <code>tests/</code>, installed <a href="https://mochajs.org/">mocha</a> and wrote a new rule:</p><pre><code class="language-bash">test:
cd tests/ && npm install && npm test</code></pre><p>As long as I have npm on my machine, I don't even need to know that there's a npm project inside this extension. I just run <code>npm test</code> and it runs the tests.</p><h2 id="adding-a-way-to-manually-visually-test-and-verify-the-extension">Adding a way to manually & visually test and verify the extension</h2><p>The way my <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/">Pokemon TCG Card Viewer</a> extension works is rather simple: when run, it detects Pokemon TCG Online codes for cards (they look like this: <code>BRS 120</code>) and adds a <code><span></code> around them. When hovered over, it'll fetch the image from API and display that next to your cursor.</p><p>That also makes automated end-to-end testing a bit challenging. So I decided to create a self-contained testing setup for manual testing and verification. To make it easier to run, I added a third make rule:</p><pre><code class="language-bash">test-visual:
cd tests/ && npm install && node_modules/.bin/http-server -p 9000 & web-ext run --bc --start-url localhost:9000 & wait</code></pre><p>First, it goes to <code>tests/</code>, installs npm dependencies and starts up a local http server in port 9000 to host a HTML file in that folder. Then, in parallel, it opens up a new Firefox instance with the extension installed, opens up a browser console and navigates the browser to localhost:9000 where the tests are served.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/11/make-visual-test-screenshot.png" class="kg-image" alt="Screenshot of page with title "Visual Testing Page for PTCGO Card Viewer", a table with a Tropius card image and text ROS 12 underlined. Next to the text, a smaller version of the same image as before next to it." /><figcaption>The test page still requires a bit of styling to make it nicer but it works</figcaption></figure><p>The test page itself contains a table with two columns: one the left, a pre-downloaded image of the card and on the right, the PTCGO code corresponding to that card. The tester can then activate the extension and hover over the highlighted codes to verify that the image loads and is the same image as in the first column.</p><p>It's still a work in progress and I'd like to make it look nicer but since it's not user-facing part of the extension, I'm not rushed with that.</p><h2 id="future-improvements">Future improvements </h2><p>For the future development, I'm also considering making the extension available for other browsers. web-ext makes development for different browsers easy with the aforementioned <code>--target</code> flag, especially when combined with value <code>chromium</code> and <code>--chromium-binary</code> flag to specify which browser to open.</p><p>Combining that with make rules, it'll be easy to have own make rules for running the extension in Firefox, Chrome, Edge and so on. And where separate versions of manifest.json or some code functionality is needed, I can adjust the <code>build</code> rule to take that into account.</p><h2 id="my-other-pokemon-tcg-tools">My other Pokemon TCG tools</h2><p>If you are a fellow Pokemon TCG player, I also recommend checking out the <a href="https://glc-checker.netlify.app/">Gym Leader Challenge Decklist Validator</a> to help you make sure your GLC decks are allowed. I collected some other tools and resources built by me and others in the community to <a href="https://hamatti.org/toolkit/">Pokemon TCG Digital Toolkit</a>.</p>
How to wait for user input from Firefox extension page before continuing
2022-11-09T00:00:00Z
https://hamatti.org/posts/how-to-wait-for-extension-page-to-be-submitted-before-continuing-extensions-execution/
<p><em>If you're new to browser extensions, check out <a href="https://hamatti.org/posts/kittens-everywhere-how-to-build-a-browser-extension/">my earlier tutorial for how to build your first extension</a> or check out <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions">the documentation and tutorials at MDN</a>.</em></p><h2 id="the-problem">The problem</h2><p>I recently got an interesting question from a student about a problem they faced when developing a Firefox extension. Here's the question, paraphrased:</p><blockquote>What I want to do is open an extension page (a user form) and wait for that page to close before having my background script continue execution. </blockquote><p>Here was their basic code flow simplified:</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">browser.browserAction.onClicked.addListener(async () => {
let { token } = await browser.storage.local.get("token");
if (!token) {
await extensionPage();
}
token = await browser.storage.local.get("token");
performAction1(token);
performAction2(token);
}</code></pre><figcaption>background.js</figcaption></figure><p>So they wanted to first check if <code>token</code> exists in the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage">extension's storage</a> and if not, open <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Extension_pages">an extension page</a> that asks for the token from the user, then retrieve it from storage and continue with the execution of the main logic.</p><p>This code above does not work. The <code>await</code> on line 4 does not actually block the execution until the extension page is closed. <em>A note here, the way this <code>extensionPage</code> function creates the extension page is via <code><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows/create">windows.create</a></code>.</em></p><p>Instead, we need to refactor and reorganize the code a bit and then we can choose from one of (at least) three different options for how to proceed.</p><p>I put <a href="https://gist.github.com/Hamatti/ad1af2d7053f52f859fe542bdd6ed794">the entire code with the three options into a gist</a> for easier reading to see how all the things come together.</p><h2 id="refactor-logic-to-its-own-function">Refactor logic to its own function</h2><p>The first thing we want to do is to refactor the main logic (other than token management) into its own function:</p><pre><code class="language-js">function mainLogic(token) {
performAction1(token);
performAction2(token);
}</code></pre><p><em>For the actual use case, the functions should be named better though. Since we don't have any domain knowledge in this example, I used vague names because naming is hard.</em></p><p>This way, we can trigger that function separately from different places which gives us more flexibility to solve our case.</p><p>The main part of the <code><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction">browserAction</a></code> listener then would be like this:</p><pre><code class="language-js"> browser.browserAction.onClicked.addListener(async () => {
const { token } = await browser.storage.local.get('token');
if(!token) {
openTokenForm()
} else {
mainLogic(token)
}
})</code></pre><p>Now we only run <code>mainLogic</code> if we do have the <code>token</code>. </p><h2 id="option-1-save-to-storage-send-message-retrieve-from-storage">Option 1: Save to storage, send message, retrieve from storage</h2><p>The first option to solve this issue is to open the extension page that contains a form to ask for the token, store it into a storage and then use <code><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage">runtime.sendMessage</a></code> to send a message that it was done to the script:</p><figure class="kg-card kg-code-card"><pre><code class="language-html"><form>
<label for="token">Token: <input type="text" id="token" /></label>
<button type="submit">Save</button>
</form>
<script src="extensionPage.js"></script></code></pre><figcaption>extensionPage.html</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-js">document.querySelector('form').addEventListener('submit', async (ev) => {
ev.preventDefault()
const token = document.querySelector('#token').value;
await browser.storage.local.set({token});
browser.runtime.sendMessage({
action: 'tokenSaved'
})
})</code></pre><figcaption>extensionPage.js</figcaption></figure><p>We would then listen for that message in our background script with <code><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage">runtime.onMessage</a></code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">browser.runtime.onMessage.addListener(message => {
if(message.action === 'tokenSaved') {
browser.storage.local.get('token').then(({token}) => {
mainLogic(token)
});
}
})</code></pre><figcaption>background.js</figcaption></figure><p>You may notice that we do an extra trip to the storage here: we just stored something into storage and then immediately went and got it from there. On some use cases, you might not need that (see next option) but it might be good if there's a case where the token might be changed by something else as this would make sure that the token is what's the latest in the storage rather than what was sent by the extension page.</p><h2 id="option-2-save-to-storage-and-send-it-in-a-message">Option 2: Save to storage and send it in a message</h2><p>The second option is mostly the same as the #1. The small change we'll make here is that we save one trip to storage and in the extension page, store it into storage and then send it as part of the message:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">document.querySelector('form').addEventListener('submit', async (ev) => {
ev.preventDefault()
const token = document.querySelector('#token').value;
await browser.storage.local.set({token});
browser.runtime.sendMessage({
action: 'tokenSaved',
token
})
})</code></pre><figcaption>extensionPage.js</figcaption></figure><p>The only change to the previous is adding <code>token</code> into the object that was sent as a message.</p><p>In the background script, we can then skip one step:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">browser.runtime.onMessage.addListener(message => {
if(message.action === 'tokenSaved') {
mainLogic(message.token)
}
})</code></pre><figcaption>background.js</figcaption></figure><h2 id="option-3-listen-for-changes-in-storage">Option 3: Listen for changes in storage</h2><p>Another option to know when the change has been made is to use a listener, specifically <code><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/StorageArea/onChanged">storage.local.onChanged.addListener</a></code>. The function provided to the listener is run every time the value changes.</p><figure class="kg-card kg-code-card"><pre><code class="language-js">browser.storage.local.onChanged.addListener(changes => {
if('token' in changes) {
mainLogic(changes.token.newValue)
}
});</code></pre><figcaption>background.js</figcaption></figure><p>Since this function is run on every change, we need to check that the value we're interested in got changed. Here, <code>changes</code> is an object that looks like this:</p><pre><code class="language-js">{
token: {
newValue: 'value-that-was-stored',
oldValue: 'previous-value-or-undefined'
}
}
</code></pre><p>The object has keys that correspond to the keys whose values got changed in the storage and two values: <code>newValue</code> and <code>oldValue</code> that correspond to whatever the new and old values are respectively.</p><h2 id="which-one-to-choose">Which one to choose?</h2><p>I would say it depends on the context and other functionality of your extension. For a simplified example like this, it does not make much of a difference. As an example, for more complex real-life extensions, it might be that the <code>token</code> can change in multiple places but you don't want to trigger this specific function every time so in that case Option 3 would not be a great choice.</p><h2 id="a-note-about-manifest-v3">A note about Manifest V3</h2><p>Above example is for Firefox MV2 extension as that is what they were building and what Firefox supports at the time of writing. If you want to do same in MV3 extension once those are available, you need to change a few things:</p><h3 id="in-manifest-json">In <code>manifest.json</code></h3><ol><li><code>manifest_version</code> from 2 -> 3 (see <a href="https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/#manifest-version">Migration guide</a>)</li><li>change <code>browser_action</code> to <code>action</code> (see <a href="https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/#browser-action">Migration guide</a>)</li><li>move <code>https://example.com/*</code> from <code>permissions</code> to <code>host_permissions</code> (<a href="https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/#host-permissions">see Migration guide</a>)</li><li>add add-on ID (see <a href="https://extensionworkshop.com/documentation/develop/extensions-and-the-add-on-id/">Extensions and the add-on ID</a>)</li></ol><h3 id="in-background-js">In background.js</h3><ol><li>change <code>browser.browserAction.onClicked.addListener</code> to <code>browser.action.onClicked.addListener</code> (see <a href="https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/#browser-action">Migration guide</a>)</li></ol><p>Those are the minimum changes needed for the extension to run in Firefox MV3 environment.</p><p>MV3 is still not fully available in Firefox so if you want to develop & test your MV3 extensions, you can use <a href="https://github.com/mozilla/web-ext">web-ext</a> with <a href="https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#run-firefox-preview"><code>--firefox-preview mv3</code> option</a>.</p><p>I usually run mine with a full command: <code>web-ext run --firefox-preview mv3 --firefox nightly --bc</code> to define <a href="https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#firefox">browser version</a>, <a href="https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#run-firefox-preview">MV3 support</a> and to open <a href="https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#browser-console">browser console</a> for easier debugging.</p><h2 id="feedback-ideas">Feedback, ideas?</h2><p>Did I miss a good way to solve this case? Or maybe I made a mistake somewhere?</p><p>Let me know in <a href="https://mastodon.world/web/@hamatti">Mastodon</a> or <a href="https://twitter.com/Hamatti">Twitter</a>. And follow me in either for more exciting tech blog posts and extension content.</p>
I joined Mastodon
2022-11-05T00:00:00Z
https://hamatti.org/posts/i-joined-mastodon/
<p>As so many others, I'm also preparing for the possible downfall of the bird site.</p><p>You can now find me also on Mastodon. If you are in that system too, you can follow me as @hamatti@mastodon.world or if you are not but would like to see what I post any way, there's an RSS feed: <a href="https://mastodon.world/users/hamatti.rss">https://mastodon.world/users/hamatti.rss</a>.</p><p>I haven't made a decision about whether to leave Twitter though so I'll continue posting to both for now (although I won't automatically cross-post everything across the two platforms). Too much of work and community related stuff happens in Twitter for me to have an easy out yet.</p>
Find assigned tasks from Google Drive
2022-10-26T00:00:00Z
https://hamatti.org/posts/find-assigned-tasks-from-google-drive/
<p>At Mozilla, we write a lot of documents. It enables us to work efficiently while having the team spread across the world. We do a lot of work through docs but also keep extensive notes about every meeting. And in these meeting notes, we assign tasks to members with the built-in mechanic.</p><p>With many tasks spread out across different documents, it's hard to keep track of them manually. Luckily, Google Drive has a search option for open assigned tasks and since I've now googled it three times because I always forgot, I'm sharing it on the blog for my and your benefit.</p><h2 id="assigning-tasks">Assigning tasks</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/10/assign-task-in-google-doc.jpg" class="kg-image" alt="Screenshot from Google Doc with an unchecked checkbox with label "Write this blog post" highlighted. Next to it, three buttons: blue plus, yellow smiley face and green pen. Next to that, an open modal with user Juha-Matti Santala assigning the task to juhamattisantala@gmail.com" /></figure><p>To assign things to people, you can leave a comment by highlighting any text, in this case a checkbox label and clicking the blue + icon that adds a comment. Inside the comment, you can tag the user with @[their email]. If you select the "Assign to [name]" box, it will be assigned to them, otherwise they just get a notification.</p><h2 id="finding-your-tasks">Finding your tasks</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/10/followup-actionitems.jpg" class="kg-image" alt="Screenshot of Google Drive with search query followup:actionitems returning one document named "Example doc for open..." with a grey badge with number 1 on top right corner of that document." /></figure><p>To find your open assigned tasks, you need to perform a Google Drive search with query <code>followup:actionitems</code> . To perform that search, you can copy-paste that into your Google Drive search field or <a href="https://drive.google.com/drive/search?q=followup:actionitems">click this link</a>.</p><p>It will show all the documents and on top right corner of each document, show a badge with number of open tasks.</p><p>For me, learning about this was such a life saver as I don't have to worry about forgetting those tasks anymore. I can start each day or week by going through all open ones, close the ones I've already completed and adjust my todo list based on them.</p>
Upcoming live stream: first look at Rocket Oct 22nd
2022-10-20T00:00:00Z
https://hamatti.org/posts/upcoming-live-stream-first-look-at-rocket-oct-22nd/
<p>I'm getting back into live streaming dev stuff and will start this Saturday, October 22nd at 16:00 CEST with a first look at <a href="https://rocket.modern-web.dev/">Rocket</a> (the Javascript one).</p><p>I first heard about Rocket from my Web Components aficionado friend Matias (<a href="https://www.youtube.com/watch?v=kd6XtCPEpsA">from codebase episode 4!</a>) and not knowing much about it, I think it makes a great subject for the first live stream after a while.</p><p>I will be looking into Rocket, reading a ton of docs and attempting to rewrite (and hopefully improve) one of my websites with it.</p><p>Join me to learn, to hang out while I code and to make helpful comments in the chat. </p><p>Youtube: <a href="https://www.youtube.com/watch?v=64RxK1xTsA4">https://www.youtube.com/watch?v=64RxK1xTsA4</a></p><p>See you on Saturday!</p>
Ad-Filtering Dev Summit 2022 Recap
2022-10-19T00:00:00Z
https://hamatti.org/posts/ad-filtering-dev-summit-2022-recap/
<p>Two conference recaps in a row?! Yes indeed, last months have been a great time with developer conferences and after organizing React Finland, I visited Ad-Filtering Dev Summit.</p><p><a href="https://adfilteringdevsummit.com/">Ad-Filtering Dev Summit 2022</a> took place in Amsterdam October 5-6th and it was my first time in this conference. After joining Mozilla as a developer advocate for Firefox add-ons in August, this event came at the perfect time. For me, it was a good opportunity to meet extension developers, people from other browser vendors and my Mozilla colleagues for the first time.</p><p><em>A quick note for those new to the browser extension world: the big talking point at the moment is <a href="https://developer.chrome.com/docs/extensions/mv3/intro/">Manifest v3</a> which is the newest version of the extension platform, introduced by Google in Chrome a while back. Different manifest versions are often abbreviated as MV2 and MV3 respectively.</em></p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/10/afds-2022-juhis.jpg" class="kg-image" alt=" Me standing outside, wearing a red cap and grey hoodie with Mozilla logo. I have conference badge with a Firefox sticker hanging from my neck. Behind me, there are the rooftops of Amsterdam." /></figure><h2 id="two-days-of-ad-filtering-content">Two days of ad-filtering content</h2><p>The event was a one-track, two-day conference, organized for the fifth time. Content-wise, it was an interesting combination of different approaches around the topic: there were technical talks about ad blockers and the manifest v3 transition, talks based on academic research around modeling and privacy and talks about policy and legislation.</p><p>Here are my light notes and observations of the talks that were most interesting to me:</p><p><strong>You broke the internet. When Content Filtering breaks a site and why it matters, Arjan van Leeuwen</strong></p><p><em><a href="https://youtu.be/yiNOaq4Py48?t=1098">Video starting at 18:18</a></em></p><p>Opera's Arjan talked about the ways ad blockers occasionally break websites. The web is an interesting medium because the page content is so flexibly editable. That is one of my favorite aspects of the browser and web but as Arjan explained in this talk, there are ways it can break when applying generic rules to variety of sites.</p><p>They can stop buttons from working or scrolling to stop functioning – for example with extensions that hide cookie consent boxes but website's implementation requires an action before the full site becomes interactable. Sometimes they can also hide checkboxes required for forms to be submitted.</p><p>All of these lead to cases where websites break and the user often doesn't know why. Disabling ad blocker (or extensions in general) is something the more tech savvy crowd tries first when debugging but for majority of the users, that's not a path they think about. They then try with a different browser (fresh install, no ad blockers) and things work and they come to the conclusion that the website doesn't work with their browser.</p><p>I think this "blame triangle" between websites, browsers and extensions is an interesting one to look into more. Sometimes it's one piece of the puzzle that fails but often it's a combination of two or three corners and for user, it's not obvious or even easy to figure out which ones are the cause.</p><p><strong>How bad is Manifest V3 exactly?, Andrey Meshkov</strong></p><p><em><a href="https://youtu.be/yiNOaq4Py48?t=3152">Video starting at 52:30</a></em></p><p>AdGuard's Andrey Meshkov presented an exploration they did <a href="https://adguard.com/en/blog/adguard-mv3.html">a while back about building an ad blocker with Chrome's MV3 implementation</a>. All in all, manifest v3 was a big discussion point in other parts of the event as well, namely the browser Q&A with Chrome, Firefox and Brave developers and hallway track.</p><p>One main takeaway was that there are two main concerns about Chrome's MV3 implementation when it comes to ad blockers: service workers with limited lifetime and declarativeNetRequest with limits for amount of filter rules.</p><p>In his talk, Andrey goes more into depth about both of those. I think the next year or two are going to be very interesting in this scene.</p><p><strong>Content Blocking Collaboration in a Manifest V3 World, Anton Lazarev</strong></p><p><em><a href="https://youtu.be/yiNOaq4Py48?t=7617">Video starting at 2:07:00</a></em></p><p>Brave's Anton Lazarev talked about manifest v3 and what alternative options extension developers have. Brave will be supporting v2 until the community has made the move to v3 and one alternative Anton talked about in his talk is for extension developers to encourage their users towards other browsers than Chrome.</p><p>The other options is to build your own browser: there are already options that are based on either Chromium or Firefox engines. Anton talks about the different browsers and options that both developers and users have in the market.</p><p>The worry is that if there becomes a drift between versions 2 and 3 in the community as that'll then require developers to maintain two versions to remain available on all major platforms.</p><p><strong>An Audit of Facebook’s Political Ad Policy Enforcement, Athanasios Andreou and Victor Le Pochat</strong></p><p><em><a href="https://youtu.be/yiNOaq4Py48?t=23714">Video starting at 6:35:14</a></em></p><p>On a very different note, I found this shared talk by Athanasios and Victor very interesting. They both did research on Facebook's policy enforcement on political ads and how inaccurate and poorly enforced it is.</p><p>If you're interested in the world of dis- and misinformation and political advertising, this talk is worth watching as a good starting point into their research.</p><p><strong>Building the internet we all want: Accurate ads measurement with no tracking, Ben Savage</strong></p><p><em><a href="https://youtu.be/nBrEGNsOJLM?t=2226">Video starting at 37:06</a></em></p><p>Meta's Ben Savage talked about research that Meta and Mozilla has been working on for <a href="https://blog.mozilla.org/en/mozilla/privacy-preserving-attribution-for-advertising/">Interoperable Private Attribution</a>. It's a way to use secrets splitting and sharing with helpers to provide measurements and impressions data for advertisers without revealing any private data from the users.</p><p>I'm by no means an expert on any of this so I'll let <a href="https://blog.mozilla.org/en/mozilla/privacy-preserving-attribution-for-advertising/">the blog post</a> explain the key points:</p><blockquote>IPA aims to provide advertisers with the ability to perform attribution while providing strong privacy guarantees. IPA has two key privacy-preserving features. First, it uses <em>Multi-Party Computation (MPC)</em> to avoid allowing any single entity — websites, browser makers, or advertisers — to learn about user behavior. Mozilla has some experience with MPC systems as we’ve deployed <a href="https://blog.mozilla.org/security/2019/06/06/next-steps-in-privacy-preserving-telemetry-with-prio/">Prio for privacy-preserving telemetry</a>. Second, it is an <em>aggregated</em> system, which means that it produces results that cannot be linked to individual users. Together these features mean that IPA cannot be used to track or profile users.</blockquote><p><strong>The irony of blocking popular trackers: People want privacy, but at what cost, Peter Lowe</strong></p><p><em><a href="https://youtu.be/nBrEGNsOJLM?t=13532">Video starting at 3:45:32</a></em></p><p>One of my favorite talks from the event was <a href="https://twitter.com/pgl">Peter</a>'s talk about his journey as a filter list maintainer and how one day he added t.co domain to the blocklist which causes no links to work in Twitter (as they use it as a bounce tracker).</p><p>I love these types of stories because they are more grounded in reality than the "best practices" types of talks we often see in conferences. And it's a view into a (small portion of a) life of an open source maintainer.</p><p>The key point Peter makes in his talk is that blocking t.co breaks Twitter essentially and a lot of people don't want that. So we as users want to block ads and tracking but only when it doesn't introduce inconvenience for us. And he makes the argument that the worry here is that if you track people in large enough scale, people are more likely to accept it since the alternative is not using the product.</p><p><strong> CSS Selector ':has()', Byungwoo Lee and Eric Meyer</strong></p><p><em><a href="https://youtu.be/nBrEGNsOJLM?t=20443">Video starting at 8:41:38</a></em></p><p>Byungwoo and Eric shared a session to do two smaller talks about the new CSS selector <a href="https://caniuse.com/css-has">:has which is currently supported</a> in Chrome, Safari, Edge and Opera and soon Firefox.</p><p>They show a lot of examples of what you can do with the selector (my note taking speed couldn't quite keep up so I just enjoyed the presentation without making notes).</p><p><strong>Browser Talk & Q&A with { Devlin, Alexandre, Simeon } from Chrome, { Rob } from Mozilla and { Anton } from Brave</strong></p><p><a href="https://youtu.be/nBrEGNsOJLM?t=27581"><em>Video starting at 7:39:41</em></a></p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/10/afds-2022-browser-talk.jpg" class="kg-image" alt="Six people on the stage, two sitting on high stools on the left, a moderator standing in the middle and three more sitting on high stools on the right" /></figure><p>Finally, there was a nice Q&A session with the browser vendors. Most of the questions were focused on the manifest v3 and especially the filter list limits. One key takeaway for me from those was that Chrome decided to start with conservative lower limits because it's easier to increase limits if need be than it is to lower existing limits. They said based on data from filter list maintainers and real performance data from Chrome users will be the needed data points to consider changing the limits.</p><p>All in all I think the Q&A was a great session. I counted roughly 21 questions and answers, depending on how you count as some of them were more akin to back-and-forth discussion but I really liked that the conference provided this platform for the discussion at the end of the conference. For me as someone new to all this, it was very informative. I learned a lot from the questions asked as well as answers given.</p><h2 id="overall-impressions">Overall impressions</h2><p>At the end of it all, it was a great event and a great trip to Amsterdam. In addition to the Ad-Filtering Dev Summit, I had a change to catch up with old friends and colleagues in the industry and talk & geek out about community, dev rel, remote work, travel and living abroad with amazing people.</p><p>The only negative I have about the summit was that the days were a bit too long. Starting at 10 and finishing the talks at 18.40 felt a few hours too much for my taste. Regardless, I'm already looking forward to the next year's event.</p><p>Thanks to all the organizers, speakers and fellow participants for great insights and friendly discussions.</p>
React Finland 2022 Recap
2022-10-12T00:00:00Z
https://hamatti.org/posts/react-finland-2022-recap/
<p>This year's React Finland was organized in September 12-16th in Helsinki and it was the first one where I was part of the organizing team, having joined in last fall. Here are my thoughts of the conference and some highlights of the talks.</p><p>I also did a similar <a href="https://hamatti.org/posts/react-finland-2021-recap/">recap last year from the 2021 online conference</a> if you want to check out the good stuff from that conference.</p><p>If you want to check out some pictures from the event and from Helsinki, check out <a href="https://www.flickr.com/photos/react-finland/albums/72177720302579786">the official conference pictures in Flickr</a> and <a href="https://www.flickr.com/photos/_delp_/sets/72177720302150173/">this incredible album by Kenneth Sutherland</a>.</p><p>There's also <a href="https://www.youtube.com/watch?v=6_hKJUdps68">the official after movie in Youtube</a>.</p><h2 id="finally-back-in-person">Finally back in person</h2><p>The last in-person React Finland took place in 2019 and after that the we spent 2020 and 2021 in different kind of online event experiments. After such a long time, it was wonderful to be in the same room with the community again.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/react-finland-2022-empty-venue.jpg" class="kg-image" alt="An empty conference hall with purple ambience coloring, hundreds of chairs and a React Finland logo on three big screens at the end of the hall" /><figcaption>The venue a night before the event. Photo by Juha-Matti Santala</figcaption></figure><p>The conference was organized at <a href="https://www.paasitorni.fi/en/">Paasitorni</a> which provides a great environment for conferences. There were so many old friends in the event that I hadn't seen either since the pandemic started or never at all before.</p><p>The ability to work remotely made it possible for me to spend a week in Finland, combining work, family celebrations, catching up with friends and organizing React Finland into a single trip with minimal time off.</p><p>One thing I'm still trying to learn during my travels and events is to take enough time off in the evenings or between-days to chill and not book my days full of events, evenings with friends or after parties and so on. If you wanna read more about <a href="https://hamatti.org/posts/traveling-in-connected-digitalized-world/">my Berlin-Helsinki-Berlin no-flying trip, I wrote a blog about it earlier</a>.</p><h2 id="highlights-of-the-talks">Highlights of the talks</h2><p>The two days of the conference were split into blocks with a few talks in each and I decided to pick one talk from each block to recommend because otherwise I'd just repeat the conference schedule and it wouldn't be much of a curated list.</p><p>You can find the full list of talks and linkes to their videos in <a href="https://react-finland.fi/2022/schedule/">React Finland 2022 website</a>.</p><h3 id="what-do-engineers-kintsugi-and-stained-glass-and-lotuses-and-clocks-have-in-common-jen-luker">What do engineers, kintsugi and stained glass, and lotuses and clocks have in common?, Jen Luker</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/jen.jpg" class="kg-image" alt="A live sketch drawing of Jen's talk" /><figcaption>Live sketch for Jen's talk by Salla Lehtipuu</figcaption></figure><p><a href="https://www.youtube.com/watch?v=0ihYcjMhM-4">Talk video</a></p><p>React Finland kicked this year's conference off with a wonderful keynote by Jen Luker. Jen talked about how we as developers are much more than just the code we write or the software engineering experience we bring to the teams. We join teams and projects as whole human beings, bringing in everything we are.</p><p>No amount of me describing the talk does justice to the talk however, so just go and watch it yourself, I'm confident you won't be disappointed.</p><h3 id="good-code-kadi-kraman">Good Code, Kadi Kraman</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/kadi.jpg" class="kg-image" alt="A live sketch drawing of Kadi's talk" /><figcaption>Live sketch for Kadi's talk by Salla Lehtipuu</figcaption></figure><p><a href="https://www.youtube.com/watch?v=NIhXfXJmJF8">Talk video</a></p><p>We spend way more time reading code than writing it. Whether it's code we wrote ourselves, code written by our current teammates or code written by those who have since left the project or the team, the quality of the code is important for future work.</p><p><a href="https://twitter.com/kadikraman">Kadi Kraman</a> gave a wonderful talk about what makes code good. And not just functionally good but good for the team to work on. Her talk was full of practical tips you can take to your project and improve your code working processes.</p><p>A very important note Kadi made is that nobody writes bad code on purpose. There are always reasons for decisions being made so the next time you're cursing on some bad code, try to remember there was a human being just like you who wrote that code with the best understanding and real-life restrictions guiding them.</p><h3 id="state-machines-meet-component-libraries-farzad-yousefzadeh">State machines meet component libraries, Farzad Yousefzadeh</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/farzad.jpg" class="kg-image" alt="A live sketch drawing of Farzad's talk" /><figcaption>Live sketch for Farzad's talk by Salla Lehtipuu</figcaption></figure><p><a href="https://www.youtube.com/watch?v=bubX1q9hrAo">Talk video</a></p><p>It's always such a pleasure to listen to Farzad talk about state machines. I've had the pleasure to do it many times and this year's talk was no different. For me, state machines is one of those technologies that I've wanted to try out for quite a long time but haven't really gotten into yet, as I haven't had a project that would have been a good fit for them.</p><p>Farzad's talk had a wonderful live coding (and live debugging with audience) part where he showed how to use the tools to define state machines both in graphical UI and in the code tying both of them together.</p><h3 id="mob-programming-woody-zuill-david-corbacho-roman-and-laura-ojala">Mob programming, Woody Zuill, David Corbacho Roman and Laura Ojala</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/mob.jpg" class="kg-image" alt="Three people sitting on chairs in the stage, their backs towards the audience. Another person stands next to them, back to audience. All of them are looking into the screen at the end of the hall. There's Python code on the screeen." /><figcaption>Mob programming live session on stage. Photo by <a href="https://www.fotocogliati.com/">Matteo Cogliati</a>.</figcaption></figure><p><a href="https://www.youtube.com/watch?v=DRj1ScxWri4">Talk video</a></p><p>For me, this session was the one I was most excited about going into the conference. I've seen Woody's talk about mob programming multiple times and it was amazing to see live in a conference. To kick off the session, Woody talked about what mob programming is and how he implemented it and seen it being implemented with different types of teams across the planet.</p><p>The majority of the session was used for a live example. David and Laura, accompanied by two volunteers from the audience, worked through a mob programming session implementing arabic-to-roman numeral conversion function. Every now and then, Woody added commentary on individual parts of the process.</p><p>I really enjoyed this showcase and would love to try out a mob programming session one day with some of my projects. Since I mostly just work on solo hobby projects, it might take a while to find an opportunity to mob though.</p><h3 id="the-sometimes-harsh-reality-of-landing-a-relevant-role-in-finland-saku-tihver-inen">The (sometimes harsh) reality of landing a relevant role in Finland, Saku Tihveräinen</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/saku.jpg" class="kg-image" alt="A live sketch drawing of Saku's talk" /><figcaption>Live sketch for Saku's talk by Linda Saukko-Rauta</figcaption></figure><p><a href="https://www.youtube.com/watch?v=YrmEF29kF1Q">Talk video</a></p><p>The second day of the conference was kicked off with a employment/working related block. As someone who recently started a new job outside Finland, it was personally the least relevant section for me but for anyone who is interested in working in Finland one day, the three talks contained a lot of good info.</p><p>Saku brought in his experience working in the field and what he has learned from the companies recruiting developers.</p><h3 id="avoiding-vendor-lock-in-through-web-components-matias-huhta">Avoiding vendor lock-in through Web Components, Matias Huhta</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/matias-huhta.jpg" class="kg-image" alt="A live sketch drawing of Matias's talk" /><figcaption>Live sketch for Matias's talk by Linda Saukko-Rauta</figcaption></figure><p><a href="https://www.youtube.com/watch?v=oy1hiAO5Cl0">Talk video</a></p><p>I'm a bit biased here but I really enjoyed Matias' talk about Web Components. Last year, I had the pleasure to host <a href="https://www.youtube.com/watch?v=kd6XtCPEpsA">a codebase session with Matias</a> where we discussed web components and built an example application with them.</p><p>For anyone who's not familiar with with web components yet, this talk is a brilliant introduction to the topic. Matias walks the listener through what web components are and why they are such a cool technology. And you can combine them with other frameworks like React too if you want to start small without a full rewrite of your systems.</p><h3 id="testing-design-systems-using-storybook-storybook-7-0-sneak-peeks-norbert-de-langen">Testing design systems using storybook + Storybook 7.0 sneak peeks, Norbert de Langen</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/norbert.png" class="kg-image" alt="A live sketch drawing of Norbert's talk" /><figcaption>Live sketch for Norbert's talk by Linda Saukko-Rauta</figcaption></figure><p><a href="https://www.youtube.com/watch?v=8ACxWOjrTfs&list=PL-a9lBflNu2ph1J-a19LNLo3GKikBDsWZ&index=22">Talk video</a></p><p>Norbert joined the conference to talk about Storybook and how it enables developers to test their components. One cool feature he showed was running interaction tests with Storybook in a way that provides visual feedback and debugging with playback controls to see exactly what goes wrong in your tests.</p><h3 id="better-accessibility-with-a-user-centric-view-marianna-sterlund">Better accessibility with a user-centric view, Marianna Österlund</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/10/marianna.png" class="kg-image" alt="A live sketch drawing of Marianna's talk" /><figcaption>Live sketch for Marianna's talk by Linda Saukko-Rauta</figcaption></figure><p><a href="https://www.youtube.com/watch?v=2GZrkN2eUiA">Talk video</a></p><p>The conference's final session block was focused on accessibility with three great talks. The one I want to highlight here was Marianna's one that used three personas, Axel, Cecily and Billy (axelcecilybilly, accessibility, get it?), all with different needs for accessible solutions.</p><p>She also used very practical examples that were easy to follow and they made it very clear to see where the issues in examples were.</p><h2 id="until-next-time-">Until next time!</h2><p>Wrapping up the conference and taking the ferry from Helsinki to Travemünde was a great finish to a great week. It was my first, third or fourth React Finland depending on how you count: 2019 I was hanging out at the after party bit star struck and afraid to speak with some of the speakers I wanted to chat with, 2020 and 2021 I sat at home watching the online sessions and engaging with the community and finally this year I got to join the team as an organizer and do my part in bringing this great event back in person.</p><p>Thanks to the team Juho, Tuuli, Aleksi, Eemeli, Harri and Toni, our volunteers, our sponsors, all the speakers and everyone I had the opportunity to chat with during the week. I had a lovely time and we'll see what future holds for the event.</p>
Set up a dashboard using Google Slides
2022-10-04T00:00:00Z
https://hamatti.org/posts/set-up-a-dashboard-using-google-slides/
<p>I <a href="https://twitter.com/Hamatti/status/1568476443830542337">recently tweeted</a>:</p><blockquote>I have been using one-slide Google Slides in a /preview mode for quite a while now as a work "dashboard", ie. "What I am working right now?". It's brilliant. Easy for me to update and with a short URL, easy for colleagues to find if they wanna know what's happening in #devrel.</blockquote><p>Part of the appeal for this is that it doesn't require any new tools, given that your organization is using Google Suite – or I'm sure something similara can be done with other providers' similar systems. No need to get new tools approved, no need to set up websites with integration to SSO and no need for your colleagues to install anything. And you can set it up in seconds.</p><p>Here's what it looks like in action:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/09/google-slide-dashboard.jpg" class="kg-image" alt="Screenshot of browser with a Google Slides preview open. Slide has Firefox logo and heading "Developer Advocacy for Firefox add-ons" with a subheading "Currently working on:" followed by a list of "Example stuff", "More examples" and "Something exciting that's going on". Footer says "Updated: 2022-09-08" and "Juha-Matti Santala | @juhis | Senior Developer Advocate for Firefox add-ons"" /></figure><p>I got this idea originally when I was working as a Developer Advocate at Futurice: I was doing a lot of things that other people in the organization were interested in but I didn't want to spam their inboxes or Slack notifications constantly when things changed. So I created a personal work "dashboard", created a branded short URL and was ready to go.</p><p>When I joined Mozilla this summer, I decided to do the same and take what I had learned from the past.</p><h2 id="here-s-how-to-do-it-yourself">Here's how to do it yourself</h2><p>The dashboard uses a feature in Google Slides that many people I've talked with are not aware of: preview mode. It's really good for these kind of one-slide, read-only things.</p><ol><li>create a new Google Slides document</li><li>make sure you only have one slide visible (you can add more hidden ones if you want, they won't show up in preview)</li><li>in the URL, change <code>edit</code> to <code>preview</code>. I think there's a way to do this from the GUI as well but couldn't find it</li><li>change sharing settings so that there's viewing permission for the link sharing to those who should be allowed to see it</li><li>create a short URL using services like <a href="https://bitly.com/">Bitly</a> or if your company has a custom one setup, use that</li></ol><p>I then add that to my Slack profile, my Mozillian page and share with my team. I make sure to regularly update it and always keep iterating to improve what is the best content and format to share.</p><p>Do note however that people can see the possibly hidden slides, speaker notes and other Slides stuff if they change the URL from preview to edit. So the preview mode is there not to hide the other stuff securely but for convenience only.</p>
kittens-everywhere – how to build a browser extension
2022-09-28T00:00:00Z
https://hamatti.org/posts/kittens-everywhere-how-to-build-a-browser-extension/
<p><em>I recently had an honor to teach a class on the basics of how to build a browser extension for Firefox. This is an article form of that class (with a few added pieces that we didn't have time to cover).</em></p><h2 id="the-goals-of-the-class">The goals of the class</h2><p>I had a few goals in mind when going into the class:</p><ol><li>I wanted to inspire students to think about what browser extensions are and what they could build with them. The imagination (and some technical constraints) are the limit for what you can do with them and getting new people into thinking about what they could build can make a big difference.</li><li>To see how the extensions are made. I decided to go with a "let's build one and see what happens" route rather than a "let's go step-by-step and learn every step completely before moving on". Hence, the content and the example extension were not exhaustive in their depth but rather a taste of what the process looks like.</li></ol><p>With these goals in mind, let's start with a quick intro and then jump into the code.</p><h2 id="prerequisites">Prerequisites</h2><p>To be able to fully follow along this tutorial, basic level of Javascript knowledge is expected, as well as familiarity with developer tools like code editor, terminal (basically how to open, navigate to folder and run commands) and browser.</p><p>If you know frontend Javascript but are not familiar with terminal use, <a href="https://www.joshwcomeau.com/javascript/terminal-for-js-devs/">Josh W. Comeau has a great guide for you</a>.</p><p>If you're new to Javascript and web development, I can recommend <a href="https://frontendmasters.com/courses/web-development-v3/">Brian Holt's great Complete Intro to Web Development in Frontend Masters</a>. </p><h2 id="what-are-browser-extensions">What are browser extensions?</h2><p>Extensions are fantastic things. They let the developers extended the functionality of the browser and the functionality of websites and web apps. Without browser extension capabilities, the users would be left with what the browser vendors and website/app creators decide for them.</p><p>By building extensions, individual developers can add or remove functionality, hide unwanted or distracting content, make browsing more private and secure and make life a bit more fun – just to name a few vague example use cases.</p><h3 id="extension-examples-by-use-case">Extension examples by use case</h3><p>Let's take a look at a few different categories of extensions that are available.</p><h4 id="adblockers">Adblockers</h4><p>One of the most popular categories for extensions are adblockers. Extensions like <a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/">uBlock Origin</a>, <a href="https://addons.mozilla.org/firefox/addon/adblock-for-firefox/">Adblock for Firefox</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/ghostery/">Ghostery</a> give power back to the users to interact with the web in a nicer, more performant and more private way. Not only can they block and hide ads from the website (making many sites usable in the first place) but they can help with blocking third party tracking. It's no wonder people love these extensions – I for sure can't see myself using the Internet without them.</p><h4 id="password-managers">Password managers</h4><p>Another category of extensions lets their users worry less about passwords. Password managers like <a href="https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/">Bitwarden</a>, <a href="https://addons.mozilla.org/en-US/firefox/addon/1password-x-password-manager/">1Password</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/lastpass-password-manager/">LastPass</a> generate new passwords when you sign up for new services, stores them securely and lets you autofill the login pages. You only need to remember the master password which helps you use stronger passwords on all the services you use.</p><h4 id="privacy">Privacy</h4><p>Privacy seems to be the battleground of the internet these days. Extensions like <a href="https://addons.mozilla.org/en-US/firefox/addon/facebook-container/">Facebook Container</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/privacy-badger17/">Privacy Badger</a> fight the good fight on users' behalf. Limiting what data gets sent from your browsing to trackers and companies is a fantastic feature. </p><h4 id="website-enhancers-modifiers">Website enhancers/modifiers</h4><p>So many sites, especially social media sites add a lot of stuff that aims to keep you engaged on the platform but can often become a distraction. And sometimes redesigns of websites can make users miss old features. Extensions like <a href="https://addons.mozilla.org/en-US/firefox/addon/minimaltwitter/">Minimal Theme for Twitter</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/reddit-enhancement-suite">Reddit Enhancement Suite</a> are built to solve that exact problem.</p><h4 id="displaying-data">Displaying data</h4><p>Some extensions are just stand-alone data displays to make it easier for you to stay up to date with what's happening in your favorite thing. I have recently been enjoying <a href="https://addons.mozilla.org/en-US/firefox/addon/footy-mate/">Footy Mate</a> that shows standings, results and schedules for English football leagues in a tidy extension pop-up with a nice and clean design.</p><h4 id="user-customization">User customization</h4><p>Extensions like <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">Greasemonkey</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/styl-us/">Stylus</a> give the customization power to the users. Instead of predefining what should happen, Greasemonkey lets you write custom Javascript and Stylus custom CSS to run on websites.</p><p>I use Styles all the time: I use it to add movie titles as text to Netflix so I can CTRL+F search them or to hide signatures from forums where people tend to use large images or animations in them.</p><h4 id="devtools">DevTools</h4><p>Browsers come with a built-in Developer Tools panel that gives access to tools relevant to developers like DOM inspector, Network tab, Debugger and so on. And when the browser's generic tools are not enough, developers can build more. <a href="https://addons.mozilla.org/en-US/firefox/addon/react-devtools/">React Dev Tools</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/web-component-devtools/">Web Components Dev Tools</a> are good examples of these – and there are more for different Javascript frameworks.</p><h4 id="adding-functionality-to-websites">Adding functionality to websites</h4><p>A quick shameless plug of my own most recent extension: <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/">Pokemon TCG Card Viewer</a> adds the ability to hover over Pokemon TCG card codes (like BRS 120) on the website and see the image of the card instead of having to try to remember what card the code refers to.</p><h3 id="different-user-interfaces-for-extensions">Different user interfaces for extensions</h3><p>Just like there are many categories of function for extensions, there are many ways the user can interact with them.</p><p>There's <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface">a great documentation page in MDN</a> that lists different user interface options for extensions. Extensions can run on page load, when <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Browser_action">activated with toolbar button</a>, through a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Popups">toolbar popup</a>, as a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Context_menu_items">context menu item</a>, as a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars">sidebar panel</a> or as <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/devtools_panels">a devtools panel</a>. They can also provide users a way to set their preferences through <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Options_pages">options pages</a>, send <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Notifications">notifications</a> and provide <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Omnibox">address bar suggestions</a>. That's quite a lot of options.</p><h2 id="let-s-build-kittens-everywhere">Let's build kittens-everywhere</h2><p><em>You can find the full code from <a href="https://github.com/hamatti/kittens-everywhere-example">github.com/hamatti/kittens-everywhere</a>.</em></p><p>Kittens Everywhere is an example extension that when activated, replaces the images on a website with cute pictures of kittens – and down the line we'll add an option for cute pictures of dogs too because we're kind like that.</p><p>But before we get there, we need to learn a few basic concepts and tools first. And build a kind of "hello world" of extensions to double check that our setup works.</p><h3 id="the-tooling">The tooling</h3><p>Extensions are built with Javascript so you'll need a code editor. You also need a browser so you can test your extension in. We'll use <a href="https://www.mozilla.org/en-US/firefox/">Firefox</a>.</p><p>Last tool that you want to have is <a href="https://github.com/mozilla/web-ext">web-ext</a>. It's not exactly necessary but it makes your developer experience so much more enjoyable with building the extension, loading it to browsers and hot reloading. It can be installed with <code>npm install -g web-ext</code> and then ran with <code>web-ext run</code> in the folder of the extension.</p><h3 id="part-1-the-very-minimum-required">Part 1: The very minimum required</h3><p>Technically all a browser extension needs to be valid and able to load into a browser is a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json">Manifest file</a> with three mandatory fields: <code>name</code>, <code>version</code> and <code>manifest_version</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
"name": "Kittens Everywhere",
"version": "0.1.0",
"manifest_version": 2
}</code></pre><figcaption>manifest.json</figcaption></figure><p>With this file, you can run <code>web-ext run</code> and the browser will happily load your extension. The extension doesn't do anything but it exists.</p><p>Manifest file is kind of a blueprint of your extension. It tells the browser basic information about the extension, which manifest version it's using (Firefox currently supports version 2 and will have version 3 coming soon; Chrome supports only version 3) and what assets and scripts it should use and where. </p><p>Let's add a few things to make it more functional.</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
"name": "Kittens Everywhere",
"version": "0.1.0",
"manifest_version": 2,
"icons": {
"48": "kittens-everywhere-icon-48.png",
"96": "kittens-everywhere-icon-96.png",
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["kittens_everywhere.js"]
}]
}</code></pre><figcaption>manifest.json</figcaption></figure><p>We added two things here: <code>icons</code> and <code>content_scripts</code>. </p><p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/icons">Icons</a> define what images you want to use for icons: these are used on the toolbar, in listing of installed extensions and so on. The guidelines suggest submitting one for 48x48 pixels and another for 96x96 and the browser will then use the one that matches best to the need. You can pick up these icons <a href="https://github.com/hamatti/kittens-everywhere-example">from the repository</a> as you build along – or make your own!</p><p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts">Content scripts</a> are scripts that run in the context of a particular website. The <code>matches</code> property is a list of <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns">patterns</a> that tell the browser where the extension should run. <code>js</code> property is a list of Javascript files that the extension will load as content scripts.</p><p>Now, let's write our first extension code to make sure everything works:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">document.body.style = "border: 5px solid green";</code></pre><figcaption>kittens_everywhere.js</figcaption></figure><p>Now run <code>web-ext run</code> and a new Firefox browser window should open up with your extension loaded. To make sure it's loaded, you can head over to <a href="about:addons">about:addons</a> and click Extensions on the left. You should now see Kittens Everywhere as an enabled extension.</p><p>If you navigate to any other website, for example <a href="https://hamatti.org/">hamatti.org</a>, you should now see a thick green border at the edge of the site. We have successfully built and loaded an extension that does something 🎉.</p><h3 id="part-2-make-it-run-when-the-icon-is-clicked">Part 2: Make it run when the icon is clicked</h3><p>We don't want our extension to run on a page load: we only want to see kittens when the other content is too serious and boring. So we need to add a few things to make the extension clickable and running our code.</p><p>In manifest.json, let's add:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
"name": "Kittens Everywhere",
"version": "0.1.0",
"manifest_version": 2,
"icons": {
"48": "kittens-everywhere-icon-48.png",
"96": "kittens-everywhere-icon-96.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["kittens_everywhere.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Kittens Everywhere",
}
}
</code></pre><figcaption>manifest.json</figcaption></figure><p>We added two new properties, <code>background</code> and <code>browser_action</code>.</p><p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Background_scripts">Background scripts</a> are friends of content scripts that have access to all WebExtensions APIs but they cannot access the content of the website. We'll get back to that a bit later. Inside <code>background</code>, we give an array of Javascript files to <code>scripts</code>.</p><p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action">Browser action</a> adds a button to the toolbar. Here, we only define the title which is displayed when hovering over the icon. From <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action">the documentation</a>, you can find how to define different icons for different cases and how to add a popup page. We won't do any of those today to keep the code simpler to follow.</p><p>Another thing we need to do is add <code>background.js</code> to our extension:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">browser.browserAction.onClicked.addListener(async function () {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
browser.tabs.sendMessage(tabs[0].id, { action: "kittenify" });
});
</code></pre><figcaption>background.js</figcaption></figure><p>Here we do three things:</p><ol><li>We listen to when user clicks on the icon (with <code>browserAction.onClicked</code>) and add a function to run when that happens (with <code>addListener</code>).</li><li>We query for the current tab.</li><li>We send a message to that tab with a Javascript object <code>{ action: 'kittenify' }</code></li></ol><p>The contents of the message are up to you as a developer. Any Javascript object works. I like to use property <code>action</code> to define what I want to be done with it but nothing requires you to use <code>action</code> specifically.</p><!--kg-card-begin: html--><section class="info">
<h3>Messaging between background and content scripts</h3>
<p>Both of our script types, content and background, have access to some things but not all. Mainly, the content scripts can access the content of the website they are run on but cannot access all WebExtension APIs. The background scripts have access to those but not the content.</p>
<p> So to get full access to everything, these two need to work together and we do that by passing messages from one to another.</p>
</section><!--kg-card-end: html--><p>One last thing before we run the new version: we need to tell the content script to listen to the message sent by background script and activate our code only when that happens:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">function kittenify() {
document.body.style = "border: 5px solid green";
}
browser.runtime.onMessage.addListener(function (message) {
if (message.action === "kittenify") {
kittenify();
}
});
</code></pre><figcaption>kittens_everywhere.js</figcaption></figure><p>We did two things here:</p><ol><li>We wrapped the original code into a function called <code>kittenify</code></li><li>We listen to messages from the background and when we get one with <code>action === 'kittenify'</code>, we run our function.</li></ol><p>If you have kept <code>web-ext run</code> running, you should be able to reload the page, click the kitten button in the toolbar and see the border appear.</p><h3 id="part-3-we-want-them-kittens-already-">Part 3: We want them kittens already!</h3><p>Okay, okay, I got you. Now we'll add the kittens!</p><p>We'll use the fantastic <a href="http://placekitten.com/">Placekitten.com</a> service. It's designed to be used as placeholder images when developing a website layout when you are lacking the real pictures.</p><p>To use it, we need to request a picture from <code><a href="http://placekitten.com/">http://placekitten.com/</a>{width}/{height}</code>. We can do that request in our <code>background.js</code>. To know what images to request for, we need to find all the images in the website with <code>kittens_everywhere.js</code> and send messages between the two.</p><p>Let's start from our content script:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">function kittenify() {
const images = document.querySelectorAll("img");
images.forEach(async function (image) {
const newImage = await browser.runtime.sendMessage({
action: "fetch",
size: {
width: image.width,
height: image.height,
},
});
image.src = newImage.imageUrl;
});
}
// rest of the code stays the same</code></pre><figcaption>kittens_everywhere.js</figcaption></figure><p>First we find all the <code>img</code> elements in the page with <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll">document.querySelectorAll</a></code> and loop over each one of those.</p><p>For each image, we take its width and height and pass those to the background script so it knows what sizes we want. When we get the answer from the background, we replace the image source with our new URL.</p><p>Let's see how the counterpart in <code>background.js</code> looks like:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">// all the other code stays the same
browser.runtime.onMessage.addListener(async function (message) {
if (message.action === "fetch") {
const { width, height } = message.size;
const resp = await fetch(`https://placekitten.com/${width}/${height}`);
const blob = await resp.blob();
const imageObjectURL = URL.createObjectURL(blob);
return Promise.resolve({ imageUrl: imageObjectURL });
}
});
</code></pre><figcaption>background.js</figcaption></figure><p>This time, our background script is the one listening to messages and when we receive one, we make a GET request to the placekitten.com URL with <code>width</code> and <code>height</code> parameters from our message. The API returns <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">a blob</a> that we convert into an ObjectURL with <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL">URL.createObjectURL</a></code>.</p><p>We then return a Promise that resolves into an object that holds our <code>imageObjectURL</code>. The reason we must return a promise is that we are inside an async function. If you're not familiar them, I recommend reading <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function">the documentation for async/await</a>, you'll use them a lot with extensions.</p><p>Now reload the page (and make sure <code>web-ext run</code> is still running; if not, restart it), navigate to a page with images (like <a href="https://hamatti.org/">hamatti.org/</a>) and click on the icon.</p><p>Images on the page should now be replaced with cute pictures of kittens. We have now built a functional extension that does something great 🎉!</p><h3 id="part-4-let-s-refactor-and-improve-our-code-a-bit">Part 4: Let's refactor and improve our code a bit</h3><p>As we've reached a nice milestone, it's a good idea to step back a bit and look how we can improve our code.</p><p>First thing I like to do is extract the URL into a global constant in <code>background.js</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">// at the top of the file
const KITTEN_URL = "https://placekitten.com"
// ... rest of the code ...
const resp = await fetch(`${KITTEN_URL}/${width}/${height}`);</code></pre><figcaption>background.js</figcaption></figure><p>Next, I like to add a check so that we only replace images that are larger than some threshold. Often tiny images are used for icons or in-site graphics and it doesn't make sense for us to replace those. This happens in <code>kittens_everywhere.js</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">// at the top of the file
const MIN_IMAGE_DIMENSION = 150;
// ... rest of the code ...
// inside the forEach
if (image.width > MIN_IMAGE_DIMENSION && image.height > MIN_IMAGE_DIMENSION) {
const newImage = await browser.runtime.sendMessage({
action: "fetch",
size: {
width: image.width,
height: image.height,
},
});
image.src = newImage.imageUrl;
}</code></pre><figcaption>kittens_everywhere.js</figcaption></figure><p>Similar to the extraction to constant with the URL, I like to put these kind of numbers (also known as <a href="https://en.wikipedia.org/wiki/Magic_number_(programming)">magic numbers</a>) into constants and give them a descriptive name. Seeing two random <code>150</code> in the code always begs the question "Why?" later.</p><p>I don't know if 150 is the right size for the threshold. That's why while we develop the extension further, we are looking at if it seems like the right images are being replaced and if not, we can adjust this one number up or down until things look nice.</p><p>Finally at this stage, I wanna do a nice-to-have feature: if we click the icon again, I want us to bring back the old images. In <code>kittens_everywhere.js</code> where we originally had <code>image.src = newImage.imageUrl;</code>, we replace that with:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">if (image.dataset.oldSrc) {
image.src = image.dataset.oldSrc;
delete image.dataset.oldSrc;
} else {
image.dataset.oldSrc = image.src;
image.src = newImage.imageUrl;
}</code></pre><figcaption>kittens_everywhere.js</figcaption></figure><p>What we do here is we store on the first run the old image source into <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes">a data attribute</a> and if on subsequent runs that attribute exists, we use that as the image source and delete the data attribute.</p><p>You can test this out by making these changes, reloading the page and clicking the extension icon a couple of times. Pictures should first become kittens and then go back on the next click.</p><!--kg-card-begin: html--><section class="info">
<h3>Debugging extensions</h3>
<p>Things don't always go the way we want them to go when developing any software. We might make a typo, forget an <code>async</code> or <code>await</code> or forget to request for some permissions in our <code>manifest.json</code> file.</p>
<p>In those cases, we need to debug! For generic tips for debugging Javascript applications, I have written a <a href="https://hamatti.org/guides/humane-guide-to-debugging/">Humane Guide to Debugging Web Apps</a> that I recommend to read. But there are a couple of special cases for browser extensions.</p>
<p>Whenever I develop extensions, I keep two Firefox windows (of the instance opened by <code>web-ext run</code>) open side by side. One has a website open where I run the extension and for another, I open the debugging site. On Firefox, you can open that on <a href="about:debugging">about:debugging</a> and selecting <em>This Firefox</em>, finding your extension and clicking <em>Inspect</em>.</p>
<p>This opens up a new developer tools instance that is connected to your background script. Now, if you do <code>console.log</code> in your background script, it will appear here. You'll also see some of the error messages in this and some in the developer tools console of the main website window.</p>
</section><!--kg-card-end: html--><h3 id="part-5-hey-juhis-what-if-i-like-dogs-more">Part 5: Hey Juhis, what if I like dogs more?</h3><p>Our extension could be finished now: it does what it promises. But I want to extend it a bit more so I can show you how to build <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Options_pages">an options page</a> where user can set their preferences for what kind of animals they want.</p><p>Let's modify the <code>manifest.json</code> to add an options page. Add this to the end of your JSON:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">"options_ui": {
"page": "options_page/options.html",
"browser_style": true
}</code></pre><figcaption>manifest.json</figcaption></figure><p>Next, create a new folder called <code>options_page</code> and two new files inside: <code>options.html</code> and <code>options.js</code>. We'll build our UI for the preferences there.</p><figure class="kg-card kg-code-card"><pre><code class="language-html"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings</title>
<style>
fieldset {
margin-bottom: 1em;
}
</style>
</head>
<body>
<p>
Do you want to see cute pictures of kittens or doggos?
</p>
<form>
<fieldset>
<legend>Choose your animal</legend>
<input type="radio" id="kittens" name="animalPreference" value="kittens" />
<label for="kittens">Kittens</label>
<br>
<input type="radio" id="dogs" name="animalPreference" value="dogs" />
<label for="dogs">Dogs</label>
</fieldset>
<button type="submit">Save preferences</button>
</form>
<script src="options.js"></script>
</body>
</html></code></pre><figcaption>options.html</figcaption></figure><p>Our <code>options.html</code> is a HTML form with two options and a button. At the bottom, we load the <code>options.js</code> where we will add these two blocks:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">async function restorePreferences() {
let localStorage = await browser.storage.local.get(['animalPreference']);
let preference = localStorage.animalPreference || "kittens" // We default to kittens
if (preference === 'kittens') {
document.querySelector('#kittens').checked = true
document.querySelector('#dogs').checked = false
} else {
document.querySelector('#dogs').checked = true
document.querySelector('#kittens').checked = false
}
}
document.addEventListener('DOMContentLoaded', restorePreferences);</code></pre><figcaption>options.js</figcaption></figure><p>Our first part restores the preferences we save into <code>storage.local</code> with key <code>animalPreference</code>. We define it to be run when <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event"><code>DOMContentLoaded</code> event</a> triggers, effectively meaning when the page has loaded and parsed.</p><figure class="kg-card kg-code-card"><pre><code class="language-js">function savePreferences(ev) {
ev.preventDefault();
const formElement = document.querySelector('form');
const formData = new FormData(formElement);
const key = 'animalPreference';
const value = formData.get(key);
browser.storage.local.set({
[key]: value
})
}
document.querySelector("form").addEventListener("submit", savePreferences);</code></pre><figcaption>options.js</figcaption></figure><p>The second part defines how we save the values into the storage. We run it when the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit">form submits</a>, we load the form into <code>FormData</code>, find our value for <code>animalPreference</code> and store that into the storage.</p><p>One last thing is required to make this functional:</p><p>We need to request for a permission.</p><!--kg-card-begin: html--><section class="info">
<h3>Permissions</h3>
<p>Not everything is available to the extensions to do as they wish. Some functionality is gated behind <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions">permissions</a> so that the user knows the extension is about to do these things. Storage is one of the functionalities for that.</p>
<p>When you build your extension, it's recommended to only ask for the minimum permissions your extension needs to work. This builds trust with your users as they don't have to wonder why your extension needs all these permissions.</p>
<p>Let me explain that through an example that might be more familiar to you. If you want to install a clock app to your mobile phone and it asks access to your contacts, your text messages and your location, you might be (for a good reason) suspicious about the app – even if it doesn't actually use those for anything.</p>
<p>So don't ask for things you don't need.</p>
</section><!--kg-card-end: html--><p>To get this permission for our storage use, we need to add a new property to our <code>manifest.json</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
"permissions": [
"storage"
]
}</code></pre><figcaption>manifest.json</figcaption></figure><p>At this point, the full manifest should look like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
"name": "Kittens everywhere",
"description": "Change images on a website into cute pictures of kitty cats - or doggos.",
"version": "0.1.0",
"manifest_version": 2,
"icons": {
"48": "kittens-everywhere-icon-48.png"
"96": "kittens-everywhere-icon-96.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"kittens_everywhere.js"
]
}
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Kittens everywhere"
},
"options_ui": {
"page": "options_page/options.html",
"browser_style": true
},
"permissions": [
"storage"
]
}
</code></pre><figcaption>manifest.json</figcaption></figure><p>If you right click your extension icon in the toolbar and select <em>Manage Extension</em> and then head over to <em>Preferences</em>, you should see a form where you can select the kittens or dogs.</p><p>Finally, we need to make the selection affect something. Let's head over to our <code>background.js</code>:</p><figure class="kg-card kg-code-card"><pre><code class="language-js">// top of the file
const DOGGO_URL = 'https://placedog.net';
// inside the `message.action === 'fetch'` if block
// replace the first line with
const { animal, size } = message;
const { width, height } = size;
let fetchURL = animal === "kittens" ? KITTEN_URL : DOGGO_URL;
fetchURL = `${fetchURL}/${width}/${height}`;
// replace the fetch call with
const resp = await fetch(`${fetchURL}`);</code></pre><figcaption>background.js</figcaption></figure><p>To pass the <code>animal</code> attribute from our storage to background, we need to adjust the <code>kittens_everywhere.js</code>. We wrap our existing code inside <code>kittenify</code> with:</p><figure class="kg-card kg-code-card"><pre><code class="language-js"> let localStorage = browser.storage.local.get(['animalPreference'])
localStorage.then(function(res) {
const animal = res.animalPreference || "kittens";
// rest of the code starting with const images = ...
// change the object we send in sendMessage to
{
action: 'fetch',
animal,
size: {
width: img.width,
height: img.height
}
}
// rest of the function
})</code></pre><figcaption>kittens_everywhere.js</figcaption></figure><p>Now the preference selection saved in the options page should reflect the animals you see when clicking the extension icon. If it's not working, check out the full code at <a href="https://github.com/Hamatti/kittens-everywhere-example/">https://github.com/Hamatti/kittens-everywhere-example/</a>.</p><h2 id="wrap-up">Wrap up</h2><p>That's a wrap!</p><p>In this blog post, we learned what extensions are and why they are awesome and how to build a browser extension that replaces images with cute pictures of kittens and doggos. To achieve that, we learned how to communicate between the content and background scripts, how to debug when things go awry and how to set up a preferences page and store the settings.</p><p>If you want to learn more about developing browser extensions, check out these links:</p><ul><li><a href="https://extensionworkshop.com/">Extension Workshop</a> is a good starting point for learning more about extensions, how to build them and how to publish them in <a href="https://hamatti.org/kittens-everywhere-how-to-build-a-browser-extension/addons.mozilla.org">addons.mozilla.org</a> and promote them.</li><li><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions">MDN Web Docs for Web Extensions</a> is the main documentation page where you can find a comprehensive API reference for extensions.</li><li><a href="https://github.com/mdn/webextensions-examples">mdn/webextensions-examples</a> has a collection of small examples that each show how to do one particular thing.</li></ul><p>And if you're looking to get involved with our great community, come to</p><ul><li><a href="https://discourse.mozilla.org/c/add-ons/35">Mozilla Discourse for Add-ons</a> is a discussion platform where you can discuss extensions and themes with other developers, ask and answer questions and help each other out.</li><li><a href="https://matrix.to/#/#addons:mozilla.org">#addons:mozilla.org in Matrix</a> is our chat room for extension developers. Same stuff as in Discourse but in real-time chat.</li><li><a href="https://blog.mozilla.org/addons/">Mozilla Add-ons Community Blog</a> is where we talk about new things, what we are doing with the community and showcase the great people involved in the community.</li></ul>
Traveling in connected, digitalized world
2022-09-21T00:00:00Z
https://hamatti.org/posts/traveling-in-connected-digitalized-world/
<p>While I was traveling back home for the first time since moving to Berlin, I realized something very fundamental. Digital and connected world has made visiting other places so much easier and convenient than it used to be.</p><h2 id="the-ease-of-buying-tickets-and-booking-hotels">The ease of buying tickets and booking hotels</h2><p>Internet and mobile apps make finding and booking transportation tickets and hotels so smooth. </p><p>My travel this time was:</p><ul><li>Night train from Berlin to Stockholm</li><li>Taxi in Stockholm from office to ferry terminal</li><li>Boat from Stockholm to Turku</li><li>Local bus from ferry terminal to city center</li><li>Local bus from city center to Naantali (and back)</li><li>Train from Turku to Helsinki</li><li>Multiple busses, trams & subway trips inside Helsinki</li><li>Boat from Helsinki to Travemünde</li><li>Bus from Travemünde terminal to Lübeck</li><li>Train from Lübeck to Berlin via Hamburg</li></ul><p>Not only can I book all these things on the fly from my phone and pay with the credit card, my phone also becomes the medium to show the tickets, check-in to boats and so on.</p><p>I have local transportation apps for a dozen or so European cities on my phone so that when I arrive, I don't have to figure out where and how to buy tickets. And pretty much all of them provide a guide feature too so I can just add in my destination and be told how to get there.</p><p>And sometimes schedules don't work and I need to make changes. This trip wasn't one of them but there have been plenty of times when I've booked a hotel via a hotel app while arriving to a new, previously unexplored city because of train delays.</p><h2 id="a-small-review-of-sn-llt-get-night-train-from-berlin-to-stockholm">A small review of Snälltåget night train from Berlin to Stockholm</h2><p>This trip, I was excited to test out <a href="https://www.snalltaget.se/">Snälltåget's</a> night train from Berlin to Stockholm. Usually I've been traveling by day which means a lot of transfers on that trip (in Hamburg, Fredericia and Copenhagen). Night train was single connection, 17.5 hours over night.</p><p>Overall, the experience was great. Despite the nightly passport control wake ups, I slept so good. I loved that I didn't have to wake up super early since the train arrives to Stockholm around 14.15. So I got to wake up slow and sit on a couch in a private compartment watching movies and staring at the Swedish countryside.</p><p>There were a few downsides though but nothing too major. </p><p>The lack of electric outlets was a bit of a let down since it meant I couldn't use my laptop. They do provide USB charging ports though so I was able to keep my iPad up and running throughout the trip while watching movies.</p><p>The beds are small – in every way imaginable. I'm ~182cm and it was barely long enough to not have to sleep in an awkward position but not long enough to really stretch myself. It was also very narrow, I think probably 60-70 cm so while lying on my back, my right arm was danling off the side. Also turning around was a bit challenging because of that, can't just roll over or you roll to the floor. And finally, the upper bunk was very close which made the experience quite claustrophobic. I did struggle with that especially in the beginning but it ended up being less of an issue at the end luckily.</p><p>Overall, like I said, it was a great experience and I'll definitely use that as often as I can when I travel between Finland and Germany. It is expensive though unless you're okay sleeping with 5 strangers in a small tight compartment, which I'm not anymore. I paid 3999 SEK (~370 euros) for a one way trip, booking the entire compartment for myself.</p><p>There was a kiosk open from Berlin to Malmö and a proper restaurant car from Malmö to Stockholm but I didn't try them out this trip. Staff was super kind and helpful and so was the border control when I was trying to find my passport in the darkness of the night when being woken up.</p><h2 id="a-small-review-of-finnlines-boat-from-helsinki-to-travem-nde">A small review of Finnlines boat from Helsinki to Travemünde</h2><p>To get back home, I decided to test out Finnlines' boat trip. I've traveled between Finland and Germany a few times with them 10-15 years ago.</p><p>In Helsinki, the boat leaves from Vuosaari which can be access with a subway + local bus combo and from the terminal, there are shuttle taxis to the boat.</p><p>The entire trip takes roughly 30 hours, the boat has a restaurant with three meals (dinner, brunch and dinner) and the food is okay. Nothing fancy but it keeps you fed throughout the trip. There's also a small cafe, small bar, a souvenirs shop, a sauna and a massage.</p><p>My biggest concern up front was 30 hours alone without Internet on a boat that doesn't have much to do. Towards the end of the trip, I did start to get a bit bored but for most part of the trip, I spent the time creating materials for a course I was lecturing in the week after. Building a browser extension that uses and external API from the Internet was a bit of a challenge without an Internet but I got most of it done and had plenty of time to fine tune the details.</p><p>Compared to the night train, there were a few big benefits. First, the room is way bigger and the bed is nicer and there's no constant fear of hitting your head to the top of the upper bed. There were also enough electric outlets so I was able to get stuff done with my laptops. And since it's a boat, there's plenty of room to walk around and eat in the restaurant.</p><p>It would have probably been super fun with a friend or two – playing games, chatting, enjoying life – but I managed to have a good time alone as well. </p><p>This time, the weather was also nice and for most of the time, if you weren't looking outside, you couldn't tell that you're on a boat. That was a big plus for me since I get sea sick rather easily which was a big concern for me.</p><p>Arriving late in the evening (21.45 on Sunday night) to Travemünde was not an optimal schedule but I booked a hotel in Lübeck and figured I'll probably find a way to get there from the terminal.</p><p>There was basically no signs or any help for finding a bus stop to get out from the terminal (and when I emailed them earlier asking for help, Finnlines replied "there's a bus stop but we don't know when buses go". To be honest, I expected a bit more customer service after paying ~400 euros for their services.</p><p>Lucky for me, there were people who knew what they were doing so I followed them to the bus stop, got to Lübeck, slept well and took the train via Hamburg to home in Berlin.</p><h2 id="having-friends-everywhere-is-a-nice-bonus">Having friends everywhere is a nice bonus</h2><p>Another thing that makes travel fun is that I have friends everywhere. When I had a few hours in Stockholm to spend between my train and boat, I hang out with friends, talked about tech and community and drank a few glasses of sparkly wine. In Helsinki, I combined work, conference organizing and meeting with friends for five days straight.</p><p>Next month I'm heading to another conference in Amsterdam and in addition to conference and new friends, will be meeting old friends for a few days. In December I head to Prague and will do the same.</p>
Why I do what I do?
2022-09-14T00:00:00Z
https://hamatti.org/posts/why-i-do-what-i-do/
<p>I'm in a very lucky and entitled situation where I get to work on things that are aligned with my values, my passions and what kind of change I want to see in the world. But what is that I do and why?</p><p>There are two main beliefs that I believe in that guide me:</p><ul><li>I believe that technology can be used for the good of humanity</li><li>In 2020s, technology is everywhere. What was an opt-in obscure hobby of some in the 1980s and 90s, is now mandatory for participating in society. Therefore, we need a diverse group of people to build those digital services.</li></ul><p>Let's take a quick detour to how I got into technology and then I'll share more about why and how what I do aligns with those two main beliefs.</p><h2 id="childhood-dreams">Childhood dreams</h2><p>I guess my first written-down "dream job" was to be Puuhanalle, the mascot for my favorite <a href="https://www.puuhamaa.fi/en/">amusement park</a> growing up. But quite quickly after that, there's only been one: to work with computers. As a kid, I didn't know that a "developer" was a thing so I never wrote that as an answer but you know, I wrote something to that vein.</p><p>I was around 2 when the first computer entered our family home and the rest is history as they say. I got completely hooked and have been ever since. There's something magical about computers and computing and the ability to create something. Especially since I've never felt I'm a creative type in a traditional art sense, writing code has been my creative outlet.</p><p>There's one story in particular that's been etched into my memory from the very early ages. My sister, who is a few years older than me, had done a picture of a couple horses with MS Paint and you had to scroll to see the other horse. My siblings kept teasing me about it and not allowing me to see the other horse. That was a fundamental moment in me understanding that computers are fascinating. I also learned to write by typing short stories that my sister would read out loud from books and I would type them to a computer.</p><h2 id="from-development-to-building-communities">From development to building communities</h2><p>My fascination of computers led me to learn programming and becoming a software developer. It took me to Shanghai, San Francisco and fun adventures in Finland. It also led me to a realization that I can have the biggest positive impact to the world if instead of me writing the code, I help others to do that.</p><p>I started building communities and teaching programming. I organize events (meetups, conferences, lunches, after works etc), run activities for online communities, write this blog, speak in events around the world, teach programming and few other things.</p><p>My main goal with these activities is this: I want to bring together people interested in and skilled in software development (and to some extent, design) so they can learn from each other, get inspired by each other and find people to get help from and to create things together with.</p><p>A nice side effect is that it also frees me from one worry: I don't need to be or become the expert, I just need to find the experts and facilitate an environment where they want to share and be part of the community. And I can drive the first belief (<em>I believe that technology can be used for the good of humanity</em>) by driving those things towards a direction that embraces that aspect.</p><h2 id="the-importance-of-diversity-in-tech">The importance of diversity in tech</h2><p>The second belief </p><blockquote>In 2020s, technology is everywhere. What was an opt-in obscure hobby of some in the 1980s and 90s, is now mandatory for participating in society. Therefore, we need a diverse group of people to build those digital services.</blockquote><p>is founded on what I've seen throughout my own experience in tech. In the early 90s, tinkering with computers was a specialized hobby that you could (given you had the financial means) choose to participate in.</p><p>In the 2000s and every day more and more, we're living in a society where it's not a choice anymore. It's not an "opt-in" and even more impactfully, it's not something you can realistically even opt out from. </p><p>It means that those who build the tools, applications and services (and those who finance it) have an immense power in shaping our world. Sometimes by deliberate intent and sometimes by not realizing it, that can lead to certain groups being excluded or having a much harder time than others using the technology and by proxy, participating in the society.</p><p>That's why I want to be part of efforts helping people from different backgrounds, life experiences and other factors to have a good chance in participating in that process of building it. It means encouraging, mentoring, teaching, organizing opportunities to learn & network & get their feet in the door.</p><h2 id="developer-advocacy">Developer Advocacy</h2><p>There wasn't a single day or single decision when I figured this stuff out. It's been an on-going process for the past 10 years as I've been teaching, writing, speaking and building communities. At one point I realized I was good at that and a bit later I realized I could get paid for that.</p><p>Developer Advocacy is one way for me to do what I do and help the developers in the community to achieve their goals while I achieve mine and the company being my salary achieves theirs. </p><p>At the time of writing this, I work at <a href="https://www.mozilla.org/en-US/">Mozilla</a> to help people developers build browser extensions, learn from each other and in the long run, help them shape the future of that technology.</p><p>And outside work, I run <a href="https://hamatti.org/p/a372022e-4dcc-4fdd-8242-0127f93c8636/turkufrontend.fi/">Turku ❤️ Frontend</a>, this <a href="https://hamatti.org/blog/">blog</a>, coach at <a href="https://hamatti.org/p/a372022e-4dcc-4fdd-8242-0127f93c8636/codebar.io/berlin">codebar Berlin</a> and <a href="https://hamatti.org/p/a372022e-4dcc-4fdd-8242-0127f93c8636/hamatti.org/speaking">speak in events</a>. Pretty much living the dream.</p>
I built a Firefox extension for Pokemon TCG players
2022-09-07T00:00:00Z
https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/
<p><a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/">Pokemon TCG card viewer</a> is a browser extension for Firefox that allows users to see the images of Pokemon TCG cards when hovering over PTCGO codes on sites like Youtube, Discord or blogs and forums like Pokebeach.</p><p>A very common way to share Pokemon TCG deck lists is to export them from <a href="https://www.pokemon.com/us/pokemon-tcg/play-online/">Pokemon TCG Online</a>, <a href="https://tcg.pokemon.com/en-us/tcgl/">Pokemon TCG Live</a> or other tools like <a href="https://limitlesstcg.com/">LimitlessTCG</a>. These are then often shared online on forums, Youtube video descriptions and Discord servers of people playing the game.</p><p>Here's what a (shortened) deck list looks like:</p><pre><code class="language-ptcgo">* 2 Blissey V CRE 119
* 3 Cramorant V SHF 54
* 1 Charmander PGO 8
* 1 Charmeleon PGO 9</code></pre><p>It's a nice and easy format for sharing but if we look at something like Expanded format (for which this list excerpt is from), there are 15 different Charmanders. And who really remembers what the Cramorant V does exactly?</p><p>So just looking at this list, it's hard to visualize what the individual cards do and which version of a card this exact set and number points to.</p><h2 id="introducing-pokemon-tcg-card-viewer">Introducing Pokemon TCG card viewer</h2><p>Luckily, nobody has to guess or dig their memory anymore. Installing the extension and clicking the button now provides the card image when hovering over the code (like <strong>SHF 54</strong> in above example) or when focusing on it with a keyboard navigation.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/09/pokemon-tcg-card-viewer-example.jpg" class="kg-image" alt="Pokemon TCG decklist in the background with Cramorant V card on top of the content as mouse hovers over Cramorant's code in the list" /></figure><p>So head over to <a href="https://addons.mozilla.org/en-GB/firefox/addon/pokemon-tcg-card-viewer/">addons.mozilla.org</a> to install the extension and let me know what you think! Best ways to do that is either <a href="https://addons.mozilla.org/en-GB/firefox/addon/pokemon-tcg-card-viewer/">leaving a review</a>, <a href="https://github.com/Hamatti/ptcgo-card-viewer-extension/issues">filing an issue</a> or <a href="https://hamatti.org/posts/i-built-a-firefox-extension-for-pokemon-tcg-players/twitter.com/hamatti">tweeting at me.</a> </p>
Hello from the new developer advocate (via Mozilla Add-ons Community Blog)
2022-09-01T00:00:00Z
https://hamatti.org/posts/external-hello-from-developer-advocate/
<p>In August 2022, I started a new job as a developer advocate for Firefox add-ons at Mozilla.</p>
<p>Read the entire blog at <a href="https://blog.mozilla.org/addons/2022/09/01/hello-from-the-new-developer-advocate/">Mozilla Add-ons Community Blog</a></p>
The Firehose
2022-08-31T00:00:00Z
https://hamatti.org/posts/the-firehose/
<p>It's been a while since the last update on this site, either on this blog or in the <a href="https://hamatti.org/weeklies">weeklies</a>. I started a new job as a Senior Developer Advocate for add-ons at <a href="https://www.mozilla.org/en-US/">Mozilla</a> in the beginning of August and it has pretty much taken all of my energy to create anything.</p><p>The firehose of information in a new job and especially in an organization (and its extended community) like Mozilla is rather exhausting. With a very good quality onboarding, there's so much general information about the organization, processes, practices that is being transmitted in the organized onboarding sessions over the first few weeks. In addition to that, there's team specific stuff like meeting your team mates, learning about what we're currently working on, trying to consume as much as possible information about the past to understand where we are going and why and picking up what has been done previously in my role and what things I could and should start doing again.</p><p>And then there's people. Add-ons teammates and different people across the organization (like other community managers, marketing and content people, product people from various parts of the company and so on) and the members of the community. Every 1on1 meeting I've had has led to me having 3-5 new names on my backlog to schedule meetings with to learn about their position, their work – and to let them know I exist if they need any help that I can offer.</p><p>It feels like after every meeting I end up with 4-5 new tabs of documents, plans, tickets, discussions and other things to read and understand at some point.</p><p>Starting at Mozilla has been a great experience and everyone has been so lovely, welcoming and helpful. It's also been a bit overwhelming: with the intensity of the firehose and trying to connect individual pieces and starting proactively to take responsibility and create interesting things for the community.</p><p>I also moved to a new country where I don't speak the local language which adds another layer of confusing thoughts and haze around my thoughts.</p><p>One tangible thing I managed to do so far that I can share is I built <a href="https://addons.mozilla.org/en-US/firefox/addon/pokemon-tcg-card-viewer/">a Firefox extension for Pokemon TCG players</a> that shows a picture of a TCG card when hovering over a PTCGO card code. I'll write more about that once I regain my creative energy and momentum for writing stuff.</p>
I wrote my first Dropzone 4 action
2022-07-28T00:00:00Z
https://hamatti.org/posts/i-wrote-my-first-dropzone-4-action/
<p>Let me tell you a quick story of a great developer experience.</p><p>I learned about, installed and got excited about two separate apps for macos today: <a href="https://iina.io/">Iina</a> and <a href="https://aptonic.com/">Dropzone</a>. </p><p>Iina is a very slick video player that looks a lot like Quicktime Player (which I've used for 90% of my local video viewing) but it has a few nice extra features like natively playing video streams from services like Youtube and others and more customization options than QT Player.</p><p>Dropzone is a menu bar tool that enables you to do quite a lot: you can temporarily store stuff (images, files, URLs, text, etc) in it or you can use it to activate different actions like moving files to folders, uploading them to internet services and more. It also allows you to build your own actions and that's where this story begins.</p><h2 id="add-to-grid-develop-action">Add to Grid -> Develop Action</h2><p>I'll have to say: I've never had a faster and better developer experience for building a custom plugin/workflow/extension ever than I had with Dropzone 4.</p><p>What I wanted to do, was to have a way to drag-and-drop a Youtube URL into Dropzone and have it open Iina with that stream. Instead of having to dive deep into developer documentation, there's a button that says "Add to Grid" which opens a menu and in addition to some pre-existing items, there's "Develop Action..." which, after a few settings clicked in the UI, opened up XCode with a template code in Python (you can also choose Ruby if you'd like).</p><h2 id="open-a-youtube-stream-in-iina">Open a Youtube stream in Iina</h2><pre><code class="language-python">import subprocess
def dragged():
url = items[0]
if 'youtube.com' not in url:
dz.fail('Not valid Youtube URL')
return
else:
subprocess.call(['/usr/bin/open', f'iina://weblink?url={url}'])</code></pre><p>The template code that Dropzone offered was good enough to give me a sense of how to write an Action in Python. I deleted it and wrote my own <code>dragged</code> function implementation (which is what gets run when something is dragged on top of the icon in Dropzone).</p><p>It gives a global (yikes!) <code>items</code> variable that contains a list of the items the user dragged in. In my case, a singular Youtube URL. I did some very crude and simplistic validation to see that I'm not sending non-Youtube links to Iina – although this doesn't actually prevent the user from dragging a non-video link like <code>youtube.com</code>. I can always fix the validation to be something smarter later.</p><p>If the URL fits, we run a subprocess to open a <code>iina://weblink</code> with our URL and voilà, it opens up Iina with the desired video.</p>
Embracing failure
2022-07-20T00:00:00Z
https://hamatti.org/posts/embracing-failure/
<p>I've been thinking about my relationship with failure a lot lately. That process has been inspired by a combination of Rach Smith's recent <a href="https://rachsmith.com/its-okay-for-me-to-be-wrong/">It's okay for me to be wrong</a>, spending time in my childhood home reflecting my upbringing and moving to a new country to start a new job.</p><h2 id="my-childhood">My childhood</h2><p>I grew up in a loving family but in an environment where experimenting and failing wasn't really encouraged but rather actively disparaged. Whether at home or at elementary school, the expectation always seemed to succeed at the first try – or be scolded for not succeeding.</p><p>I remember very little of "no worries, you tried! let's try again and see if we can learn from it" mentality from my childhood. I'd try something and if it didn't go well, I'd move on to something else because clearly I wasn't good enough for the initial thing.</p><p>That led to me, in my teenage years and even early adulthood to avoiding everything I wasn't already great at. And that probably set me back for a good decade on many things and had an everlasting negative impact on my self-esteem – one that I still haven't figured out.</p><p>I really wish now that as a child, I would have gotten more encouragement and support for failing because it would have very likely made my self-esteem healthier and it would have led to me learning much more early on.</p><h2 id="learning-to-experiment">Learning to experiment</h2><p>It was during my university years when I started hanging out with the local startup people that I started to unlearn that old mindset of mine and adopting a new one. One of experimentation, <a href="https://hamatti.org/posts/learning-in-public/">learning in public</a>, and excitedly moving towards new things no matter how good I might be at them in the beginning.</p><p>This blogging and public speaking endeavour has definitely been one. Building communities has been another one. And the adventurous mindset of not worrying too much about practical things and just moving forward and figuring things out. If at first I don't succeed, usually I'll get another chance and even help from people when I dare to ask.</p><p>The biggest influence to that change in my mindset was spending more and more time with people who already had and lived through that perspective. People who weren't afraid to fail, who looked at each new adventure as an opportunity to learn. By through social osmosis, I slowly started to do the same. In an environment where most people were open to failure, it even started to feel like I'm losing out on something if I keep avoiding those opportunities just because I'm afraid of failing.</p><h2 id="varying-levels-of-failure">Varying levels of failure</h2><p>Of course, there are many levels of failure, some more significant in impact than others. It's a different thing to risk and lose all of your money if you need to pay mortgage and take care of your family than it is to risk burning a few potatoes when you're trying a new recipe.</p><p>I have noticed that this is what makes the general discussion of failure so challenging. When you hear the startup mantra of "fail fast, fail often", some think of the mortgage, some the potatoes. To me, the whole idea is that it's better and safer to fail early when the stakes are small – and not when failure eventually means losing everything. But it also means that one can fail many more times on the path to mastery or figuring out what one wants to do.</p><h2 id="it-ain-t-ever-easy">It ain't ever easy</h2><p>Even though I wrote this post about how I learned to embrace failure, it needs to be said that it's by no means easy. Everytime I start something new, I'm terrified of failing: starting a new job, moving to a new country, joining a new social club or anything else. </p><p>I guess the mindset shift has been mostly to do those things regardless of the fear.</p>
Unit test your Python code in Jupyter Notebooks
2022-07-13T00:00:00Z
https://hamatti.org/posts/unit-test-your-python-code-in-jupyter-notebooks/
<p>In December of 2021, I participated in <a href="https://adventofcode.com/">Advent of Code</a>, an annual Christmas calendar of programming puzzles. Each year I try to learn something new or hone a specific part of my skills. In 2021, I decided to learn how to use <a href="https://jupyter.org/">Jupyter Notebook</a> and focus on writing and explaining my solutions in addition to coding them.</p><p>It turned out quite well, I enjoyed the experience and you can see the results (in static, non-runnable versions) from <a href="https://hamatti.org/adventofcode/2021/">my Advent of Code 2021 page</a>.</p><p>One thing I did not do though, was to write any proper unit tests for my code. I was focusing on solving puzzles (not my strong suite) and learning a new tool so I relied on manual testing by running the code with various inputs by hand. I did that partly because I didn't have time and energy to figure out how to properly do unit testing inside Jupyter Notebooks.</p><p>Come summer and I have that time and energy! So let me take you to a journey with three different solutions for how to keep your Jupyter Notebook code tested.</p><h2 id="doctest">doctest</h2><p>The first solution is to use <a href="https://docs.python.org/3/library/doctest.html">doctest</a>, a Python testing suite, that relies on your function documentation for tests. Here's what it looks like in practice:</p><pre><code class="language-python">def sum(a, b):
"""Addition of two numbers.
>>> sum(0, 0)
0
>>> sum(1, 2)
3
>>> sum(1, -2)
-1
"""
return a + b</code></pre><p>Each test starts with <code>>>></code>, followed by the function call and then the following line has the desired result. It looks the same as if you'd use Python's REPL.</p><p>Once you have these functions with doctest compatible docstrings written in your notebook, you can then add a separate cell to run them:</p><pre><code class="language-python">import doctest
doctest.testmod()</code></pre><p>I think doctest is a suitable option for simple functions with limited corner cases to test. I like that the tests are documented right along-side with the function definition itself so they are easy to find, it makes it easier to see how the function is intended to use and for simple use cases, it's effortless to write them.</p><h3 id="debug-printing-in-doctest">Debug printing in doctest</h3><p>One downside or annoyance I had with doctest had to do with debug printing. When a test would fail, I would go into the code, add debug print statements to see what was going wrong. But as doctest tests based on the stdout output, a debug print would make every test fail:</p><pre><code class="language-python">import doctest
def sum(a, b):
"""
>>> sum(1, 2)
3
"""
print(a, b)
return a + b
doctest.testmod()</code></pre><p>The above would fail as doctest would compare <code>3</code> with <code>1, 2\n3</code>.</p><p>In December 2022, I learned that I can get around this by printing out to stderr instead:</p><pre><code class="language-python">import doctest, sys
def sum(a, b):
"""
>>> sum(1, 2)
3
"""
print(a, b, file=sys.stderr)
return a + b
doctest.testmod()</code></pre><p>Now the test passes <strong>and</strong> I get the debug prints!</p><h3 id="downsides-or-challenges">Downsides or challenges</h3><p>However, it starts to fall apart with more complex functions that have even a bit more arguments or lots of corner cases. It makes the docstring long and hard to read, especially since you don't usually get syntax highlighting for the docstring.</p><h2 id="unittest">unittest</h2><p>Second option is to use <a href="https://docs.python.org/3/library/unittest.html">unittest</a>. It takes a different approach from doctest that you write your tests in separate test classes that are then run. It offers a clean interface and enables you to give your tests proper names so it's easier to find the failing test case.</p><pre><code class="language-python">def count_increases(data):
"""Advent of Code 2021, Day 1, Part A"""
increase_count = 0
prev = None
for measurement in data:
if prev and measurement > prev:
increase_count += 1
prev = measurement
return increase_count</code></pre><p>Let's say we have the above solution from Advent of Code's 2021 Day 1 puzzle and we want to test it. In a separate cell (for example, at the bottom after all the logic code), we can write the tests:</p><pre><code class="language-python">import unittest
class CountIncreaseTestCase(unittest.TestCase):
def test_empty_has_none(self):
self.assertEqual(count_increases([]), 0)
def test_one_measurement_has_none(self):
self.assertEqual(count_increases([199]), 0)
def test_three_ascending_has_two(self):
self.assertEqual(count_increases([199, 200, 201]), 2)
if __name__ == '__main__':
unittest.main(argv=[''], verbosity=2, exit=False)</code></pre><p>Here we can group our tests based on the functionality we're testing: this class contains tests for the part A where we count increases and each method is a separate test case for a specific corner case.</p><p>It's important to note that since we're running this inside Jupyter Notebook, you need to provide <code>argv=['']</code> and <code>exit=False</code> to <code>unittest.main</code> or otherwise it'll error out.</p><p>Using unittest is a great approach but it can make the notebooks harder to read if there's a lot of tests at the end or sprinkled in. They do bring a lot of value in showing the reader/future developer that a) tests exists, b) where they are and c) they can be run by executing the cell.</p><h2 id="testbook">testbook</h2><p>Third option is to use <a href="https://github.com/nteract/testbook">testbook</a> that is a library written for this exact use case: testing Jupyter Notebooks. You'll write your functions as usual inside the notebook(s):</p><pre><code class="language-python">def count_increases(data):
"""Advent of Code 2021, Day 1, Part A"""
increase_count = 0
prev = None
for measurement in data:
if prev and measurement > prev:
increase_count += 1
prev = measurement
return increase_count</code></pre><p>and then write the tests in regular Python files and testbook enables you to reference functions inside notebooks:</p><pre><code class="language-python">from testbook import testbook
@testbook('testing.ipynb', execute=['count_increases'])
def test_empty(testbook):
func = testbook.get('count_increases_testbook')
assert func([]) == 0
</code></pre><p>In this example, I have my notebook called <code>testing.ipynb</code> and I have <a href="https://jupyterbook.org/en/stable/content/metadata.html#adding-tags-using-notebook-interfaces">tagged</a> the cells (in this case, one cell tagged <code>count_increases</code>) that I want the test to execute before running the test. If you want to run all of your code, you can replace the argument with <code>execute=True</code>. </p><p>With <code>testbook.get</code> function, I can access functions from within the notebook and then use whatever test suite I want to test. In this case, I have a <code>assert</code> at the end of the test function and I can run it with <a href="https://docs.pytest.org/">pytest</a> with <code>!pytest</code> inside a cell and it will run the terminal command pytest which in turn will run (by default) all the files that match the pattern <code>test_*.py</code> or <code>*_test.py</code>.</p><p>Great value in testbook is that I can keep the tests in a separate file and run them with whatever test framework and tooling I want but unfortunately it is a bit cumbersome that you have to jump between interactive notebook and terminal just for the tests. This also introduces a bit of friction to test writing and running, meaning it might become too much of a temptation (or just forgetting) to add tests.</p><h2 id="conclusion">Conclusion</h2><p>After I spent a while playing around with these three different options writing tests for my last year's Advent of Code code, I'll probably use unittest in the future. </p><p>testbook had a lot of potential ideas but in the context of what kind of code I see myself writing within notebooks in future, it's probably not the best option.</p><p>doctest has its place but unfortunately the bar for when the test cases become too complex for it is too low and it can make reading the functions too messy for it to be for my taste. If you know a good open source project that is using doctest extensively with complex stuff, I'd love to know so <a href="https://twitter.com/hamatti">tweet at me</a>!</p><h3 id="few-more-jupyter-notebook-tricks">Few more Jupyter Notebook tricks</h3><p>If you'd like to learn more about Jupyter Notebook, I recommend reading <a href="https://valohai.com/blog/jupyter-notebooks/">Juha Kiili's recent blog post Five things to know about Jupyter notebooks</a>.</p>
Appendix A to Developer's Guide to Communities
2022-07-08T00:00:00Z
https://hamatti.org/posts/appendix-a-to-developers-guide-to-communities/
<p><a href="https://hamatti.org/posts/developers-guide-to-communities/">Earlier this week I wrote about my thoughts and experiences about developer communities</a>. Then something happened yesterday that is a story worth sharing in an appendix.</p><p>This is a story about how meeting people and being active can lead to fun stuff.</p><p>Let's start by rewinding bit over 6 months, to December of 2021. I was sitting at home when I saw a ping in Slack: someone in a global community sent a message saying they're looking for some likeminded people in Helsinki to join for a beer. Someone else remembered I'm from Helsinki and tagged me.</p><p>Few DMs later, I was sitting on a tram to a pub and met two guys, had some beers, laughed a lot and had a generally very good time. At the end of the night we parted ways and didn't talk after that.</p><p>Fast-forward back to today. I moved to Berlin on Wednesday and figured I'd like to meet some new people. What a better way to make new friends than join the local board game groups on the very next day, Thursday. And I did. I signed up for a game of <a href="https://boardgamegeek.com/boardgame/231581/auztralia">AuZtralia</a> (which now has an unfortunate name but awesome gameplay and Cthulhu theme) and showed up to the place.</p><p>And guess who was not only in the board game night but also hosting the game I signed up for? The friend of the dude from the beers. We played a good game, over 3.5 hours and we had a lovely time.</p><p>You never know who you'll meet if you're active and you never know where those connections and friendships might lead you in the future. I'm sure I would have had a great night regardless but meeting and playing with someone I knew already made it so much easier and so much more fun.</p>
Developer's Guide to Communities
2022-07-06T00:00:00Z
https://hamatti.org/posts/developers-guide-to-communities/
<p>
Software development is such an interesting industry that there are so many
communities that have been formed around it: meetups, conferences, online
communities, blogosphere, social media and so on. The amount of people who
like to share what they know, have learned and have experienced is
mind-blowing to me and something that makes me very happy.
</p>
<p>
I ended up getting involved with developer communities slowly and in the
beginning, bit awkwardly but ended up falling in love with the concept so much
that my career took a turn from development of software to development of
communities. But I'm still also a developer who participates in these
communities and have been pondering for years about writing a "guide" for
developers on how and why to get involved with developer communities.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/07/dev-lunches.jpg" class="kg-image" alt="Four panel picture with each of the panels showing a different group of people eating lunch" />
<figcaption>
In October '21, I attended four dev lunches in Helsinki, Oulu, Tampere and
Turku
</figcaption>
</figure>
<h2 id="why-should-you-as-a-developer-get-involved-in-developer-communities">
Why should you as a developer get involved in developer communities?
</h2>
<p>
There are many reasons for people to be involved in developer communities –
and a decision not to be involved in them is also a perfectly fine and valid
one. You can be a great developer and have a successful career without ever
being involved with any communities. However, here are a few reasons why I
recommend people getting involved.
</p>
<h3 id="meet-peers-and-build-relationships">
Meet peers and build relationships
</h3>
<p>
First great reason is that developer communities give you an opportunity to
meet with people who have something in common with you. You don't need to be a
24/7 developer-for-lyfe type of person to benefit from having a platform
outside your current job/team/project to discuss technological topics.
</p>
<p>
I have made so many friends all over the world through meetups, hackathons,
conferences and online communities and I am so happy I have because people are
kinda the best part of life.
</p>
<p>
In addition to making friends, these communities are also a great way to build
<strong>professional relationships. </strong>The industry is so networked
(people, not wires) and getting to know people outside your job can have a
huge impact in your career: what kind of technologies are they using, what
kind of processes they use at work, how's their team culture, how much they
earn and so on. Learning these over time from others in the industry helps you
improve your team's situation and gives you tools to reflect on your career
situation and future path.
</p>
<h3 id="new-opportunities">New opportunities</h3>
<p>
Another effect that comes from building professional relationships is that
you'll learn about new opportunities – and equally important those new
opportunities learn about you!
</p>
<p>
Let's imagine a situation that you're interested in switching jobs. If all of
your connections in the industry are within your current job, you're pretty
much limited to job ads and sending your CV to those. In my experience, many
of the best opportunities never get to the stage of open job ads but are
rather filled directly.
</p>
<p>
On the other hand, if you're involved in your local communities, you can start
having discussions about your interest for something new during the breaks at
meetups/conferences with people you've gotten to know or you might end up in
discussions where people share about the needs their teams are having. I once
ended up in a great interview for a job after a manager mentioned they were
looking for someone with my skill set – while we were playing Dungeons and
Dragons during Christmas.
</p>
<p>
Being part of the community also brings another huge advantage: social proof.
As you interact with people, get involved in communities and contribute to the
community as a speaker or organizer yourself, people will learn more about you
and you build trust that makes it more likely that people want to work with
you.
</p>
<h3 id="growth-opportunities">Growth opportunities</h3>
<p>
Becoming a speaker or hosting a session in your community can be a great
opportunity to grow as a professional and to expand your skillset – not to
mention building your <em>personal brand.</em>
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/07/juhis-first-conf-talk.jpg" class="kg-image" alt="A still from conference recording with picture of me in the top left corner and my live coding session shared on the right" />
<figcaption>
My first ever conference talk, a live-coding session at PyCon Finland 2016
</figcaption>
</figure>
<h4 id="a-side-note-about-personal-brand">
A side note about <em>personal brand</em>
</h4>
<p>
Personal brand is a term that has somewhat negative connotations to many. In
my experience many people associate it with ideas like "presenting yourself as
something you're not" or "being arrogant" but in a nutshell in this context,
this is how I see personal brand:
</p>
<p>
<strong>It's a curated view on things that you've done, are good at and are
interested in.</strong>
</p>
<p>
You don't need to (and should not) lie or create fake brand around yourself
but rather share what you're already doing and curating it so that it shows
your best side and focuses on things you want people to know about you.
</p>
<p>
You could be the absolute best in the world in something but if nobody in the
world knows it, your options on what you can do with that skill are limited.
</p>
<p>
To build your personal brand, you can share what you've worked on
<a href="https://twitter.com/hamatti">in social media</a>, build a website for
yourself (like this site of mine that you are on right now),
<a href="https://hamatti.org/blog">write blog posts</a>,
<a href="https://hamatti.org/speaking">do talks in events</a> and help people
in your community with skills you have.
</p>
<p>
In a way, everyone has a personal brand: it's what other people know and think
about you. And if you're not doing anything about it yourself, you're leaving
it to the interpretation of others and that can lead to less desireble
results.
</p>
<p>
Here's a practical example of that curation: in my previous job as a developer
advocate at Futurice, one part of my job was to run our university and student
collaboration. It wasn't the main part, not the most impactful nor the most
interesting part of my job but because it was the easiest part to explain, I
often started my internal introductions with that. As I noticed that people
started seeing me (and even introducing me) as mainly that, I realized that I
needed to change the way I talked about my work to highlight the more
important parts.
</p>
<h4 id="continuing-on-growth">Continuing on growth</h4>
<p>
There's so much more to software development than just writing code. In 2019,
I gave
<a href="https://hamatti.org/talks/i-teach-therefore-i-learn/">a presentation about how teaching has helped me improve as a developer</a>
in various events.
</p>
<p>
First skill that is universally valuable and becoming more and more important
these days is <strong>communication skills</strong>. As a developer, you need
to constantly communicate your work with teammates, managers, clients, users
and so on in different ways and levels. Giving a talk in your local meetup or
writing a blog post about what you know is a great way to practice
communication skills.
</p>
<p>
Doing presentations on technical topics also helps you deepen your
understanding on the topic. In
<a href="https://twitter.com/reverentgeek/status/1110568292597579776">his tweet, David Neal put it very nicely</a>:
</p>
<blockquote>
1. Speaking is hard work. But, it's the kind of hard work that is *good*. It
requires you to dive deeper into the topic and to think more deeply about it.
Along the way, you will likely learn new things and discover new insights.
</blockquote>
<p>
Every time I've made a presentation, I've done a bit (and sometimes, a lot) of
extra research to make sure I understand the topic even better (and deeper
understanding leads to better communication). And every time you strengthen
your core understanding and skills, you become more confident as a developer
and you can dive deeper into your craft.
</p>
<p>
And these presentations don't even need to be meetup/conference talks but the
same things apply to all sharing: it could be a blog post or helping people in
Slack/Discord/Matrix and participating in discussions there.
</p>
<h2 id="how-to-get-started">How to get started?</h2>
<h3 id="how-to-find-developer-communities">
How to find developer communities?
</h3>
<p>
There's no one complete index where you can find them all. Different
communities take form and share about their activities in different ways.
However, there are some popular places to look for:
</p>
<p>
<a href="https://www.meetup.com/">Meetup.com</a> is the market leader in
meetup directory space. There you can search for meetups based on geography
and content from a collections of hundreds and even thousands of groups.
</p>
<p>
<a href="https://meetabit.com/">Meetabit.com</a> is one competitor especially
popular in Finland and you can find many meetup communities, filtered by their
cities there.
</p>
<p>
<a href="https://github.com/thisdot/tech-community-slacks">thisdot/tech-community-slacks</a>
is a community-curated repository of different online communities for
developers around the world.
</p>
<p>
<a href="https://cult.honeypot.io/reads/6-best-frontend-communities-to-join-in-2022/">.cult also listed 6 online communities for frontend developers</a>, maybe you can find some you like there.
</p>
<p>
And a lot of educators (like for example,
<a href="https://wesbos.com/">Wes Bos's</a> community for people who've bought
his courses), programming streamers and other active people have their own
great communities.
</p>
<p>
Some communities share their events in Facebook, LinkedIn, Twitter and other
social media. In those, I recommend searching for tech keywords and following
people active in the industry. Little by little, you start building a good
understanding of the communities in your area and interest area.
</p>
<p>
Also asking your colleagues is a good way to get started. Ask which meetups or
conferences they attend or which online communities they are active in.
</p>
<p>
For my readers in Finland, there are two active general online communities
that I can recommend:
<a href="https://koodiklinikka.fi/">Koodiklinikka</a> (from our #tapahtumat
channel you can find a lot of tech events organized in Finland) and
<a href="https://mimmitkoodaa.fi/">Mimmit Koodaa</a>. Through these two
communities you can find a lot of other, more technology specific groups and
events as well.
</p>
<h3 id="how-do-meetups-work">How do meetups work?</h3>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/07/rust-talk.jpg" class="kg-image" alt="Me doing a talk in front of a small audience at Futurice office with slide behind me saying "Lightning talk: rustlings"" />
<figcaption>Me doing one of my talks in Rust Finland meetup</figcaption>
</figure>
<p>
I'm using meetups as the example here because I find them the easiest way to
get started with local communities.
</p>
<p>
If you have never been to a meetup, here's a quick generic intro to how most
of the meetups operate. There are always some individual differences but this
should give you a good overview and more confidence in joining.
</p>
<h4 id="who-can-attend">Who can attend?</h4>
<p>
Unless specifically specified, meetups are usually open for everyone and
welcome students, beginners, hobbyists, juniors, seniors, and anyone
interested in the topic to join.
</p>
<p>
I highly recommend especially students and juniors to start attending meetups
early on since they are a great place to build relationships with people in
the industry and learning about stuff that happens in real life software
development projects way before you end up building them yourself.
</p>
<h4 id="registration">Registration</h4>
<p>
Almost all in-person events (and some online events too) require you to
register through their platform of choice (meetup.com, meetabit.com,
eventbrite, facebook/linkedin events, google forms etc) so that should be your
first step. If the event is already full, many of these platforms keep a
waitlist from where you may get a spot even at the last minute when
cancellations occur (and they occur quite a lot).
</p>
<h4 id="structure-of-events">Structure of events</h4>
<p>
Most common structure for a meetup is to have one or more prepared talks (or
panel discussions, general discussions, demos and so on) and time for hanging
out, chatting, getting to know each other and so on.
</p>
<p>Often there's also food and drinks offered but not always.</p>
<p>
<strong>As a participant, you don't need to know anything about the topics</strong>: there's no entry exams so feel free to participate even as a student or
junior or if you're generally interested in the topic.
</p>
<p>
Back in the day when I started participating in meetups, I used to sit in the
corner, eat a slice of pizza, listen to talks,
<a href="https://hamatti.org/posts/shy-introverts-short-guide-to-speaking-in-conferences/">be too afraid to talk to anyone</a>
and then leave. I learned about tech, got inspired to try new things and over
time, I got more confidence to participate in discussions and started to get
to know people.
</p>
<h4 id="hallway-track">Hallway track</h4>
<p>
The talks are the most visible part of meetups but the best part of the
meetups is what happens before, between and after the talks – also known as
the <strong>hallway track. </strong>
</p>
<p>
So unless you're busy to do other stuff, I recommend not leaving when the
talks finish but sticking around for discussions, getting to know people and
having a good time. Sometimes the events also continue after the official
program with after "party" (meaning usually sitting around in a
pub/cafe/restaurant to discuss) somewhere outside the venue.
</p>
<p>
This applies to conferences too and even more, the bigger the event is. You
don't have to sit and listen to each and every talk. I used to do that when I
started going to conferences but then I was often very tired and exhausted by
the sheer amount of sitting and listening that I didn't have much energy for
the social part.
</p>
<p>
I've been to hundreds of events in the past 10 years and I honestly don't
remember many of the talks that well – but I do remember fondly so many good
casual discussions during and after the events.
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2022/07/codebar-hug.jpeg" class="kg-image" alt="A group of people sitting on a multilevel auditorium staircase hugging the air towards the camera" />
<figcaption>
A #FridayHug with our coaches and students in a codebar event
</figcaption>
</figure>
<h3 id="find-your-own-community">Find your own community</h3>
<p>
There's a lot of communities out there. You don't need to worry about being
involved in all of them but I recommend checking out a few around the topics
that you're most interested and find the ones that you like the best.
</p>
<p>
I do recommend to also experiment a bit with communities outside the core
technologies you work with since quite often the problems we solve are
universal regardless of the tech used and people are similarly awesome in
different communities.
</p>
<p>
So if you're mostly working with React during the day and participate in your
local and/or global React community, visiting for example a Vue meetup can
help you learn about new things and meet great people who work with similar
problems than you.
</p>
<p>
Same applies to backend: if you're into building backends with Python, you
might learn something great in a Ruby or .NET meetup. It's also very common
for there to be talks that are more generic than the topic of the meetup
itself.
</p>
<p>
And in the ever-changing world of technology, it's very likely you end up
working with more than one language or framework during your career so
learning about the options is very helpful.
</p>
<h3 id="get-more-involved-with-your-community">
Get more involved with your community
</h3>
<p>
Once you've found your way into the communities and want to do more, there are
a few great ways to deepen your involvement.
</p>
<h4 id="present-a-talk-in-meetup">Present a talk in meetup</h4>
<p>
I already talked about this earlier in the blog post but a great way to get
more involved is to go from being a participant to becoming a speaker. Get in
touch with your meetup's organizer and ask if they'd be interested in having
you speak in their events. Depending on the size and activity of the
community, you might get a speaker slot for the following month's event or for
one six month's down the line.
</p>
<p>
You don't need to be the top expert in your topic to share. In my experience,
most meetups are very welcoming for new speakers and you can always talk from
your perspective and your skill level – you don't need to pretend to be
anything more than you are. Often the crowd is mixed-level too so having more
beginner-oriented talks are often great for those who are starting their
journey.
</p>
<p>
For example, I've done two talks in Rust meetups despite being a very much
beginner in the language: in
<a href="https://www.youtube.com/watch?v=v4t4pWIxz8I">Rust Denver, I shared my learning journey</a>
and in
<a href="https://www.youtube.com/watch?v=r0gQcSqZ3nI">Rust Finland I talked about a cool project (rustlings) that helps people
learn Rust</a>. Both were well received and I had a great time getting more involved with
the community.
</p>
<p>
A nice side effect is that once you start doing talks, you're likely to be
asked to be involved in other meetups, podcasts or other community activities
too, especially in smaller countries or cities where there is not unlimited
supply of people interested in speaking about technology.
</p>
<h4 id="help-organizing-things">Help organizing things</h4>
<p>
Joining the group of people running the community is another great way. Most
developer communities are run by volunteers on their own time so help with
finding sponsors and speakers, hosting events, managing recordings/livestreams
doing social media or marketing and updating the website is often much
appreciated.
</p>
<p>
It's usually easier to start doing that once you've gotten to know the people
in your community and have participated in few events but there's no harm in
reaching out to the organizers at any point and asking how you could help.
Best communities are community-run and it's a great way to start a new hobby
too.
</p>
<h4 id="help-other-members-of-the-community">
Help other members of the community
</h4>
<p>
A key characteristic of a community is that it's a group of people doing
something together and helping each other out. It's not just a few people
organizing stuff for others but people coming together and helping each other
out on grassroots level.
</p>
<p>
So if you can help someone with their tech problems or questions in the
discussion boards or chat platform, awesome! Your help is much appreciated.
For example, in our Koodiklinikka Slack community, I love how you can get help
for almost any problem you have in a few minutes at almost any hour of the day
or day of the week. People are helping out each other and making the community
better for everyone.
</p>
<h3 id="a-few-words-about-tech-conferences">
A few words about tech conferences
</h3>
<p>
Conferences are very similar to meetups in most of the ways mentioned above,
just bigger in scale. Instead of a 2-3hr evening event, conferences often take
anything from 1 to 7 days and bigger events have multiple tracks of talks and
hundreds or thousands of participants. And they most often cost money to
participate.
</p>
<p>
Hallway tracks, getting involved by volunteering and joining the organizing
team, learning from talks and getting to know great people are the same
though. Conferences are a wonderful place to also expand your networks with
people from around the world compared to local meetups.
</p>
<p>
Check if your company offers learning budget or other budget for participating
in conferences and join one somewhere nearby. Personally conferences are one
of my favorite things these days: I get to combine traveling abroad with
meeting new people all while learning new things about the things I work with.
</p>
<p>
Best way to find conferences is to use your favorite search engine and search
for "[your tech] conference" and go through the options. There are plenty!
</p>
<p>
My recommendation, if possible, is to arrive a bit early (previous afternoon)
and leave a bit later (the following afternoon) to maximize the opportunities.
I like to invite fellow conference-goers for dinners the night before or lunch
the day after to have more time to get to know them and have great
discussions.
</p>
<p>
There's also often either official or ad-hoc unofficial after parties after
the last day so I don't recommend booking your flight or train home right
after the talks end or you're missing out the best parts.
</p>
<h2 id="best-time-to-start-is-now">Best time to start is now</h2>
<p>
I highly recommend joining developer communities. Personally, I've gotten so
much out of them – most of the best things in my life are thanks to being
active in communities.
</p>
<p>
So stop worrying, check your local events from resources above and get
involved. There are also some cool projects being built to make it easier for
people to get started like
<a href="https://www.conferencebuddy.io/">Conference Buddy</a>.
</p>
<p>
Once you get started, you'll quickly notice that it's a lot of fun and brings
a whole new aspect into your professional and personal life. And then you
wonder why you didn't start earlier.
</p>
<h2 id="appendix-a-a-real-life-story">Appendix A: A real life story</h2>
<p>
After initially publishing this blog post, I had an encounter that reminded me
why being active and meeting people is so cool. So I wrote and
<a href="https://hamatti.org/posts/appendix-a-to-developers-guide-to-communities/">Appendix A</a>
to share that story.
</p>
Javascript's console is so much more than just console.log
2022-06-29T00:00:00Z
https://hamatti.org/posts/javascripts-console-is-so-much-more-than-just-console-log/
<p>If you've ever written Javascript for more than just the first few lines, you've very likely learned about <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/console/log">console.log</a></code>. It's a function that prints out its arguments into the console and is very handy for many things. I recently wrote a blog post about <a href="https://hamatti.org/posts/print-is-your-best-debugging-tool/">why <code>print</code> statements are the best tool for debugging</a> and this blog post is a continuation on that, diving bit deeper into debugging via print in Javascript land.</p><h2 id="why-debug-via-print">Why debug via print?</h2><p>A quick recap for those who didn't read the earlier blog post. Printing is great for a few reasons:</p><ol><li>It's fast and straight-forward to write: it'll take only a few seconds to type something like <code>console.log("I AM HERE")</code> and then re-run your app.</li><li>It doesn't require any setup: you don't need to set up logging environment or figure out how to plug in your IDE's or editor's debugger into the currently running app. </li></ol><p>These two reasons are the main causes for why I always start with print. I'm more likely to get started and I can spend more time and energy figuring out the problem rather than getting annoyed by setting up something more complex.</p><h2 id="say-hello-to-console">Say hello to <code>console</code></h2><p>When I started programming in Javascript back in the day, <code>console.log</code> was just something you learned early on and it always felt like a single command, kinda like <code>print</code>. Only after a while, I realized and learned that there are two parts: <code>console</code> and <code>log</code> which then opened the door to Narnia for me: there's more to <code>console</code> than just the <code>log</code> function.</p><p>In this blog post, we'll take a look at some of the great features and functionalities that exist within this realm.</p><h3 id="console-log">console.log</h3><p>Let's start with the most commonly known one, <code>console.log</code>. As said, it prints the arguments into the console. If you're developing a frontend application with Javascript, it'll appear in the console inside the Developer Tools of your browser if you have them open.</p><pre><code class="language-js">console.log('I AM HERE');</code></pre><p>Continuing from my example from the previous blog post, the simplest use of the function is to pass it a string or number literal. It's very useful for confirming that we are in the right place.</p><p>You can also pass in variables or expressions:</p><pre><code class="language-js">let number = 42;
console.log(number);
console.log(number * 2);</code></pre><p>which prints 42 and 84.</p><p>Sometimes you want to combine the two and there are a few ways in Javascript to do that:</p><pre><code class="language-js">let name = 'Juhis'l;
console.log('Hello', name, '!');
console.log('Hello ' + name + '!');
console.log(`Hello ${name}!`);</code></pre><p>There are minor differences here: the first one prints a whitespace between each argument so it'll come out as <em>Hello Juhis ! </em>where as the other two will print <em>Hello Juhis!</em></p><p>I personally prefer the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">template literal</a> syntax which is the last of the above example. It makes things easy to read and modify as you don't need to start and end quotes all the time.</p><h4 id="console-log-with-dom-elements-and-objects">console.log with DOM elements and objects</h4><p>A few special cases come when we want to print out DOM elements and Javascript objects:</p><pre><code class="language-js">const someDiv = document.querySelector('#someDiv');
console.log(someDiv);</code></pre><p>When you print out an element with <code>console.log</code> in the browser's console, you'll see an "interactive" DOM representation where you can expand and collapse sections. <em>You can get a JSON object style output if you use <code>console.dir()</code> instead!</em></p><p>For logging objects, you may encounter unexpected results:</p><pre><code class="language-js">let person = {
name: "Juhis",
site: "https://hamatti.org",
favoritePizzaTopping: "pineapple"
}
console.log(person);</code></pre><p>This will print what you expect in the beginning but if the underlying object changes, the output will change too as it's a "live" view into the object's state.</p><p>To counter this, you can either do the classic JSON stringify-parse trick or create a deep clone with <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/structuredClone">structuredClone</a></code>:</p><pre><code class="language-js">let person = {
name: "Juhis",
site: "https://hamatti.org",
favoritePizzaTopping: "pineapple"
}
console.log(JSON.parse(JSON.stringify(person)));
// or
console.log(structuredClone(person))</code></pre><h4 id="multiple-variables-with-a-nicer-output">Multiple variables with a nicer output</h4><p>Sometimes you have a small collection of variables that you want to print out. I've found the best way (meaning, most readable output) to do this is with <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer" rel="noreferrer">object property value shorthand</a>:</p><pre><code class="language-js">let name = 'Juhis';
let site = 'https://hamatti.org';
let favoritePizzaTopping = 'pineapple';
console.log({name, site, favoritePizzaTopping})</code></pre><p>This creates a temporary object and prints the object out with key-value pairs so you don't need to manually do any additional strings for keys. Very handy!</p><h4 id="style-it-with-css-">Style it with CSS!</h4><p>Another cool feature – that I have used very rarely though – is adding CSS styling with <code>%c</code> identifiers:</p><pre><code class="language-js">console.log('%cJuhis%c is %ccool', 'font-weight: 900', 'font-weight: initial', 'color: green');</code></pre><p>Each <code>%c</code> starts a new block and after the string to be printed out, each parameter styles one block.</p><h3 id="console-table">console.table</h3><p>Now that we got the basics out of the way, let's get to know some of the friends of the <code>log</code> from the <code>console</code> family, starting with <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/console/table">table</a></code>.</p><p>As the name implies, <code>table</code> is for printing tabular data:</p><pre><code class="language-js">let matrix = [[0, 0, 1], [0, 1, 0], [1, 0, 0]];
console.table(matrix);</code></pre><p>By providing <code>console.table</code> an iterable, in this case an array of arrays, it'll print out a beautiful table.</p><p>You can also provide it an object:</p><pre><code class="language-js">let person = {
name: "Juhis",
site: "https://hamatti.org",
favoritePizzaTopping: "pineapple"
}
console.table(person);</code></pre><p>and it'll give you a tabular representation with keys and values.</p><p>This is a major improvement over <code>log</code> when looking into bit larger objects, their shape and contents.</p><p>If you have an array of objects, you can spesify a second parameter (<code>columns</code>) to provide which properties you want to print out.</p><pre><code class="language-js">let person = {
name: "Juhis",
site: "https://hamatti.org",
favoritePizzaTopping: "pineapple"
}
let person2 = {
name: 'Definitely human',
site: 'https://developer.mozilla.org/en-US/docs/Web',
favoritePizzaTopping: 'web'
}
console.table([person, person2], ['name']);</code></pre><p>That comes handy if you have a long list of large objects.</p><h3 id="console-count">console.count</h3><p>Sometimes we aren't actually interested in the contents of the data (or there's too much of it) and we just need to know how many times something happens, for example in a complex looping and branching structure.</p><p>In that case, we bring in the <a href="https://www.britannica.com/topic/count">roalty</a> of the family: <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/console/count">console.count</a></code>. </p><pre><code class="language-js">let fruits = ['apple', 'banana', 'pear', 'orange'];
let vowels = ['a', 'e', 'i', 'o', 'u'];
fruits.forEach(fruit => {
if(vowels.includes(fruit.charAt(0))) {
console.count('Starts with a vowel');
} else {
console.count('Stars with a consonant');
}
});
/** prints out: **/
// Starts with a vowel: 1
// Stars with a consonant: 1
// Stars with a consonant: 2
// Starts with a vowel: 2</code></pre><p>This is handy for debugging more complex structures as it gives you a ballpark figure to start with: if you think something should happen 50 times but it happens twice or that things should happen roughly 50:50 but the ratio is 1000:1, you know something's wonky.</p><h3 id="console-group">console.group</h3><p>Last but not least of the functions I'm introducing in this blog post is <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/console/group">console.group</a></code>. Sometimes following the output can become bit messy if there's a lot of nested stuff. That is often a good indicator that some refactoring might be in order but sometimes in legacy projects you don't have that luxury so you just want to get things debugged.</p><p><code>console.group</code> can be used in combination with the other ones as it only provides a "configuration" as it tells the console to indent following console prints one level. A corresponding function is <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/console/groupEnd">console.groupEnd</a></code> that closes the group and returns the indentation back one level.</p><pre><code class="language-js">function catchEmAll(pokemon) {
console.group('At function body')
console.log(`Gotta catch 'em all`)
console.group('Inside loop')
pokemon.forEach(mon => {
console.log(`I choose you, ${mon} ${mon} ${mon}!`)
})
console.groupEnd()
console.log(`Function catchEmAll has finished`)
console.groupEnd()
}
catchEmAll(['Bulbasaur', 'Squirtle', 'Charmander'])
catchEmAll(['Articuno', 'Zapdos', 'Moltres'])</code></pre><p>This outputs the following (in Google Chrome, at the time of writing):</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/06/console-group-output.jpg" class="kg-image" alt="A console output with three levels of indentation, group headers bolded." /></figure><h3 id="get-to-know-the-rest-of-the-family-">Get to know the rest of the family!</h3><p>These are not the only methods in the <code>console</code> object - just the ones I use most often when I'm debugging. To get to know the rest, I recommend checking <a href="https://developer.mozilla.org/en-US/docs/Web/API/console">MDN's doc page for console</a>.</p><p>Happy debugging!</p>
A lighthouse
2022-06-22T00:00:00Z
https://hamatti.org/posts/a-lighthouse/
<p>On top of a small hill, there's a small lighthouse. At the base of the lighthouse, there's a small house, and at the heart of it there's a fireplace that brings light and warmth to the entire house. Even on the rainy days – and there are plenty – the house stays dry and comfy.</p><p>After I enter through the door, drop the rucksack on the bench, and light the fire in the fireplace, an inner peace fills me. Whenever I arrive, there's no more rush anywhere. Just me and the vast roaring ocean and the lighthouse.</p><p>The downstair house is very simple. A small bedroom with a great bed and a nightstand; a kitchen on one side of the main room that shares a cooking top with the fireplace; a large kitchen table with two long benches near the windows that open up to the ocean. In the middle of the room, there's a large rug on the floor that brings extra warmth on cold evenings.</p><p>On the outside, there's a small porch with a beautiful view to the sea. There's nothing better on a rainy day than to lie on the porch on a hammock while listening to the raindrops hit the tin roof of the porch. And on those rare days when the sun is shining, the tin roof provides just enough sunscreen to keep the porch a perfect spot for chilling outside.</p><p>Climbing the stairs inside the round tower, I reach the top with a large light. It's an old light that needs to be turned on and off manually every day. That mundane but impactful task brings much needed structure to the days. Every morning, I start the day climbing the stairs to turn the light off and every night, I repeat the process to turn it back on for the night. There's not much else in the room. A few tools for moments when mechanical parts need fixing.</p><p>I love that lighthouse. Every time I visit, I calm down and get to chill. </p><p>There's only one problem. </p><p>That lighthouse does not exist. I only get to visit it when I close my eyes.</p>
Goodbye, Futurice
2022-06-15T00:00:00Z
https://hamatti.org/posts/goodbye-futurice/
<p>
Time for some personal news. It's time for me to say goodbye to Futurice after
a bit over 4 years. A lot has happened and a lot has changed over these 4+
years and I like retrospectives so here's one.
</p>
<p>
What I'll be doing next, you might ask? I'll share that a bit later but I'll
be leaving Finland and embarking on a very exciting next adventure that'll
challenge me in all new ways. I'm both excited and terrified.
</p>
<h2 id="first-some-highlights">First, some highlights</h2>
<p>
I did quite a lot of fun and challenging stuff over these years, here are some
of my personal highlights:
</p>
<h3 id="people">People</h3>
<p>
It's no secret that for a lot of people, me included, the people are the best
part of working at Futurice. I got to work with – and more importantly become
friends with – a lot of amazing people, absolute superstars professionally and
some of the kindest humans I've ever met. With some, we only worked for a
short time, with some I got to work with for the full 4 years.
</p>
<p>
One of the best parts of my role was that I got to work with almost everyone:
developers, designers, marketing, recruitment, human care – both in Helsinki
and globally. And especially the ability to learn from all these really top
notch professionals in their own fields.
</p>
<p>
And it's not just for the fun stuff. A great friend and a co-worker is one
that travels to the office with cinnamon buns when I'm having a mental
breakdown and crying in the corner of a meeting room. Or a supervisor who day
after day, week after week, and year after year is always there to support me
through all the good and bad. Or friends coming together for a birthday lunch
to celebrate the fun days.
</p>
<h3 id="tech-weeklies">Tech Weeklies</h3>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/05/techweeklies-feb2020.jpeg" class="kg-image" alt="Bruno doing a Tech Weeklies presentation in our Helsinki office with a small crowd watching" />
</figure>
<p>
When I joined Futurice in the spring of 2018, I had already been running Turku
❤️ Frontend for a few years and it was quite a dream come true for me to be
able to run an internal weekly meetup, also known as
<a href="https://futurice.com/techweeklies">Tech Weeklies</a>. I ended up
doing 12 talks + hosting a bunch of jam sessions in addition to MC'ing
somewhere between 100-150 sessions during these four years.
</p>
<p>
One thing that makes me happy was being able to
<a href="https://futurice.com/blog/tech-weeklies-as-a-learning-platform">help new people practice public speaking, get excited about it and even
become meetup and conference speakers</a>. Seeing people grow like that is so rewarding.
</p>
<h3 id="the-jolts">The Jolts</h3>
<p>
A small but incredibly impactful part of Futurice's culture that I want to
bring with me to wherever I go is the Jolt culture. Named after
<a href="https://en.wikipedia.org/wiki/Jolt_Cola">Jolt Cola</a>, Jolt is the
culture of giving public thanks and positive feedback to colleagues. Not only
during performance reviews or formal occasions but during the everyday work.
</p>
<p>
We had a Jolt emoji in Slack, a huge never-ending roll of paper hanging in the
wall and a station of goodies - stickers, candies, cards and other tokens of
appreciation - that we were encouraged to use to build an environment where we
remember to say the good things that often are left unsaid. Every day when you
come to the office, you can see the roll of paper filled with written thank
yous by colleagues and Slack is full of wonderful messages of thanks and
public praise.
</p>
<p>
I had never experienced that anywhere else before and I don't want to ever
work without something like that again so who ever I end up working with in my
life in the future, you can be sure I'll bring the Jolts with me.
</p>
<h3 id="dev-breakfast">Dev Breakfast</h3>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/05/story-dev-breakfast.png" class="kg-image" alt="A collage of colorful banners with various titles of articles, blog posts and tech talks" />
</figure>
<p>
Another highlight is the
<a href="https://futurice.com/devbreakfast">Dev Breakfast</a> newsletter we
started in the summer of 2019 and I got to run it for full 3 years before
leaving. A monthly developer newsletter which also gave us an opportunity to
highlight the great people I worked with and share a very diverse view of what
is interesting to developers.
</p>
<p>
Dev Breakfast got a friend in 2022 when the company launched
<a href="https://futurice.com/design-breakfast">Design Breakfast</a> which to
me was a strong sign that we had built something great with the developer
newsletter.
</p>
<p>
One thing is for sure, I'll be continuing to subscribe to both of them after I
leave.
</p>
<h3 id="global-code-camp">Global Code Camp</h3>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/05/code-camp-2019.jpg" class="kg-image" alt="A group of developers and designers gathered around in a room full of screens, sofas and gadgets" />
</figure>
<p>
Global Code Camp brought together developers (and some designers) for an
internal weekend hackathon. It was a long tradition at Futurice and my first
one as an organizer was in 2019 when we went to Tallinn with ~35 Futuriceans.
We hacked on AR/voice recognition/image recognition etc technologies and had a
blast
<a href="https://www.erinevatetubadeklubi.ee/en">in the best event space in Tallinn</a>.
</p>
<p>
Our plan for 2020 was to go to Mathildedal to hack with alpacas but then the
pandemic threw in a lot of problems and we postponed to 2021, then 2022 and
now I don't get to see the alpacahack. But it did strengthen a lot of
friendships and did create a inside hype with Futupaca stickers.
</p>
<h3 id="student-event-force">Student Event Force</h3>
<p>
I've always loved to work with students (my previous job being running a
student startup community at Boost Turku and another previous job and on-going
hobby being teaching programming) so being team member at the beginning and
for the past 3 years leading the Futurice's Student Event Force was an awesome
opportunity.
</p>
<p>
Tons of student events and opportunities to help students learn something new,
to get in touch with experienced developers in the industry and so on. And we
did a really really good job, as evident from constant top results in surveys
like YPAI and Universum; one highlight there being that we beat Google as the
most desired workplace amongst young professionals in 2020 for the first time
(finished 2nd in the survey).
</p>
<p>
Student Event Force was also a really great opportunity to experiment with
things and to be proactively involved in the student community. I have so many
fond memories from late evenings at the office sitting down and having great
discussions with students.
</p>
<p>
I also got to lecture at the university, host workshops at the office as part
of different university programs and courses, and a ton of other stuff.
</p>
<p>
Especially I wanna say hi and thanks to my favorites, Athene, Atkins and Hive.
Everything that I got to do with you was so much fun. ❤️
</p>
<h3 id="spice-program-hacktoberfest-and-docsthursday">
Spice Program, Hacktoberfest, and DocsThursday
</h3>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/05/docsthursday-working.jpg" class="kg-image" alt="A group of developers in a meeting room, around a table, working on their laptops and discussing" />
</figure>
<p>
Open source communities have long been close to my heart and at Futurice I got
to do so much in this space. Our
<a href="https://spiceprogram.org/">Spice Program</a> fell into my hands at
some point and I got to inspire people to contribute to open source and share
their work – as well as<a href="https://futurice.com/blog/how-spice-program-supported-my-creation-of-235">
log a lot of Spice Hours for my own projects too</a>.
</p>
<p>
Annually we had really good events during
<a href="https://hacktoberfest.digitalocean.com/">Hacktoberfest</a> and even
though my goal of eventually having a Hacktoberfest event in each of our
offices didn't come to reality, the ones we had were fun. Best was 2019 when
we had one-day hack days in Helsinki and Tampere and a meetup in Munich.
</p>
<p>
And in 2019, when Ricardo N Feliciano launched concept called DocsThusrday, I
really wanted to get involved and
<a href="https://futurice.com/blog/improving-documentation-of-open-source-software">we organized a couple of afterwork sessions</a>
for discussing about open source, documentation and worked on different
projects.
</p>
<h3 id="supporting-local-communities">Supporting local communities</h3>
<p>
Over these years I got to host a lot of different meetups at our office,
helping local communities to organize events. Hundreds of people came,
learned, got inspired and left with a smile on their faces from these
developer gatherings. The pandemic threw a wrench in those for a while and we
experimented with bad success with some online events that didn't really do
the magic.
</p>
<p>
To mention a few I had great memories from: Rust Finland, Python Helsinki,
HelsinkiJS, Google Developer Group Helsinki, Azure & Friends, AWS User
Group Finland, codebar, Helsinki Clojure meetup, Clojurebridge, Account
Management meetup, React Helsinki, React Finland, Service Design Network,
DotNeat, Elm meetup, Tampere.NET are some of the events we hosted or sponsored
and there are so many additional ones where I got to speak or otherwise
represent the company.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/05/helsinki-dev-lunch.jpg" class="kg-image" alt="A group of developers eating lunch around a long table" />
</figure>
<p>
And during my time at Futurice I started new community thing myself too.
Helsinki Dev Lunch became the one lunch each month that I looked forward to.
</p>
<p>
And when the pandemic kept local events at bay, I got to experiment with
<a href="https://hamatti.org/codebase/">codebase livestreams</a>.
</p>
<h2 id="second-what-s-next">Second, what's next?</h2>
<p>
I need to pack my life into a backpack (so excited for that), sell/donate some
stuff from my apartment, enjoy summer holiday season and then take the ferry
and the train to some new adventures.
</p>
<p>
I'll be reading a few books, catching up with friends and family, watering a
few potatoes in the field and relax for a while before in August new fresh
start.
</p>
print is your best debugging tool
2022-06-08T00:00:00Z
https://hamatti.org/posts/print-is-your-best-debugging-tool/
<p>I'm a huge fan of debugging. To me, it's one of the most enjoyable aspects of software development. For the past few years, I've been spreading my love for debugging in various meetups, workshops and conferences, most recently at HelPy meetup and <a href="https://2022.djangoday.dk/talks/juhis/">Django Day Copenhagen conference</a>.</p><p>Each time I've given the talk, it has sparked great discussion: some people agree, some disagree and sometimes we get to have great in-depth discussions about the topic.</p><p>This week, instead of rehashing my talk into a blog post as-is, I'll focus only one small part of it: the print statement. In your language, it may come in dressed as <code>console.log</code>, <code>print</code>, <code>println</code>, <code>printf</code>, <code>Console.WriteLine</code>, <code>echo</code> or some other form – regardless of its form, we're talking about the same concept: printing something into the console.</p><h2 id="the-superpowers-of-print">The Superpowers of print</h2><p>Many might not consider <code>print</code> to even be a <strong>debugging tool</strong> and that's a fair assesment. One could argue that the category starts from more advanced and specialized tools: something like <code>debugger;</code> in Javascript or <code>breakpoint()</code> in Python that halts the progress of the program and gives you access to inspect and interact with the code.</p><p>However, from my point of view, <code>print</code> is a tool that can help you debug and hence I'll categorize it as a debugging tool. Regardless, that semantic is beyond the point. We're here to talk about how and why is <code>print</code> so powerful.</p><p><strong><code>print</code> is not all-powerful. </strong>It's a very simple tool that does one thing. And the simplicity is the beauty of <code>print</code>.</p><p>First, it only takes a few seconds to write <code>print('I AM HERE')</code> which is kinda where I often start.</p><p>There's this brilliant meme that I originally found from <a href="https://twitter.com/JenMsft/">@JenMsft's Twitter</a>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/06/debug-meme.jpeg" class="kg-image" alt="Four panel meme: 1st panel, an evil cartoon character points up with grim next to a whiteboard that says "need to debug an issue". 2nd panel, the board says "set a breakpoint to get started", 3rd panel, the board says "the breakpoint never hits" and the 4th channel, the board says "the breakpoint never hits" and the evil character looks towards it awkwardly" /><figcaption>from<a href="https://twitter.com/JenMsft/status/1256007715425382400/"> https://twitter.com/JenMsft/status/1256007715425382400/</a></figcaption></figure><p>I've seen something similar happen often with developers of every skill level: they see a problem, they dive immediately into the code, try to reason with the code, change something and nothing changes in the software.</p><p>A single print, that takes a few seconds to write, can confirm to you that you're in the right part of the code. It's the developer equivalent of asking our parents "make sure you've plugged it in" when they complain the printer isn't working. Our brain keeps telling us, surely we know that simple thing but we quite often don't.</p><p>Secondly, the print is easy to read. All you need to do is to keep an eye on the console and see if you see your print output. No need to dig into some logs like if you'd set up logging and no need to halt the execution of the program and run commands like with debuggers. It's especially nice because you don't need any infrastructure set up. </p><p><strong>Not having to set up anything means you can debug any legacy software in pretty much anywhere.</strong></p><p>Printing being such an integral part of any programming language, it also means that you can effectively start debugging software even if you're not very familiar with the technologies used. Granted, that happens rarely in real life but as someone who teaches and mentors a lot of juniors, it has been handy.</p><p>I've heard from few sources that some senior developers have been teaching juniors to avoid printing. I can only guess it comes from the dislike of print statements ending up in production but it's unfortunate if the side effect is that new developers skip printing altogether, even when it's hugely beneficial.</p><h2 id="shortcomings-of-print">Shortcomings of <code>print</code></h2><p>Even though <code>print</code> is the your best tool, it's not perfect.</p><p>There are often cases when print is not enough and you need more powerful tools like debuggers, or it's better to setup something that is always running like logging and monitoring to give you a head start for debugging when problems occur.</p><p>One major downside of <code>print</code> is that you need to run the software (or the functionality you are debugging) over and over again everytime you want to change what you're outputting.</p><p>Also, if you have too many print statements, the output can become hard to follow. And debugging more complex objects or expensive function calls can be cumbersome with <code>print</code>.</p><p>When any of those issues comes marching towards you, reach for the more specialized and powerful tools. But until it, don't belittle <code>print</code> but instead make it your best friend. <strong>Because it's fast, simple and inherently powerful tool.</strong></p>
GLC Decklist Validator updated with Astral Radiance
2022-06-01T00:00:00Z
https://hamatti.org/posts/glc-decklist-validator-updated-with-astral-radiance/
<p>In August 2021, I built and published the first version of <a href="https://glc-checker.netlify.app/">Gym Leader Challenge Decklist Validator</a>. It's a webapp that allows Pokemon TCG players who build decks for <a href="https://gymleaderchallenge.com/">community-made Gym Leader Challenge format</a> to validate that their decklists are legal for the format. Back in August, I <a href="https://hamatti.org/gaming/the-best-way-to-play-pokemon-tcg/">wrote about the format and why I think it's currently the most enjoyable way to play the game</a> but I apparently I never wrote an introduction post to this project.</p><p>I did <a href="https://hamatti.org/posts/hello-code-my-old-friend/">write about my rewrite project</a> when I took the early prototype and completely rewrote it to make the project what it is today.</p><h2 id="introducing-glc-decklist-validator">Introducing GLC Decklist Validator</h2><p>GLC Decklist Validator is a vanilla Javascript project that consists of three main parts</p><ol><li>an admin toolkit for downloading card info from API, managing ban list and so on</li><li>a serverless Netlify Function that validates the list</li><li>and a web frontend which consists of one text area and performs one API call to the serverless function</li></ol><p>So it's exactly the kind of project I thrive to build: as small and simple as possible.</p><p>The project is MIT licensed open source project and its code is available in <a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator">hamatti/gym-leader-challenge-deck-validator</a>.</p><h3 id="admin-tooling">Admin tooling</h3><p>This project uses the <a href="https://pokemontcg.io/">PokemonTCG.io</a> API but not live directly from the web UI. Instead, I have built a custom tooling on the backend.</p><p>First part is <a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator/blob/main/tooling/cli.js">a CLI </a>that provides an interactive menu to choose operations: downloading new sets, inspecting current flat JSON "database" and managing the ban list. I use <a href="https://www.npmjs.com/package/inquirer">inquirer</a> library for building interactive menus.</p><p>A key reason why I chose to build an interactive CLI tool instead of one that relies on arguments and options was that the admin side is run once every 3 months – when a new set is published – and very rarely other than that. I wanted a tool that can guide me through the process rather than having to browse through the docs everytime.</p><p>The aptly named <code><a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator/blob/main/tooling/utils.js">utils.js</a></code> is the powerhouse that performs the actions. I decided to built this intermediate step for two reasons: first, the live web app is not dependent on if the API is up and available or not and second, because querying up to 60 cards in one swoop is not that feasible with the API.</p><p>Finally I have <code><a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator/blob/main/tooling/build.js">build.js</a></code> that copies the data into the serveless function's folder.</p><h3 id="web-frontend-serverless-function">Web frontend & serverless function</h3><p>The <a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator/blob/main/netlify/functions/validate/validate.js">serverless function</a> takes in a decklist in PTCGO format (see example for example in <a href="https://hamatti.org/posts/syntax-highlight-all-the-things/">my blog post about custom syntax highlighting</a>), parses it and checks each card for validity on various metrics: rulebox, set legality, singleton, banned and monotype.</p><p>The function then returns a legality boolean with info of all the cards that fail the check.</p><p>The web frontend provides a text area for deck list and upon receiving the information from the serverless function, displays whether or not the deck is valid and what, if any, concerns it has.</p><h2 id="updated-for-astral-radiance">Updated for Astral Radiance</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/05/radiant-pokemon.png" class="kg-image" alt="new Radiant Pokemon cards, Greninja, Heatran and Hawlucha" /></figure><p>Last Friday, the newest Pokemon TCG set Astral Radiance was released and it introduced a new type of rulebox Pokemon, <em>Radiant Pokemon, </em>that is not allowed in GLC so in addition to my regular update of downloading new cards, I had to add a new check into the validator:</p><pre><code class="language-js">if (
(subtypes &&
(subtypes.includes("EX") ||
subtypes.includes("GX") ||
subtypes.includes("BREAK") ||
subtypes.includes("V") ||
subtypes.includes("VMAX") ||
subtypes.includes("VSTAR"))) ||
rarity === "Rare ACE" ||
rarity === "Radiant Rare" ||
name.includes("◇")
) {
ruleBox = true;
}</code></pre><p>Given that the official format and its cards are referring to rule boxes as a mechanic, I hope one day the API would have that information directly to avoid mistakes with compound checks like these.</p><h2 id="go-test-it-out">Go test it out</h2><p>If you're a Gym Leader Challenge player or interested in the format, give the deck validator a go at <a href="https://glc-checker.netlify.app/">https://glc-checker.netlify.app/</a>. And if you want to learn more about the format, <a href="https://gymleaderchallenge.com/">https://gymleaderchallenge.com/</a> is your best starting point.</p>
Summer reading for scifi fans
2022-05-25T00:00:00Z
https://hamatti.org/posts/summer-reading-for-scifi-fans/
<p>I always plan to read more when summer holiday approaches, then spend the first few days reading books but then over time I read less and less. Well, summer is approaching again and I'm looking to something new to read.</p><p>Luckily I'm part of Finnish software developer community <a href="https://koodiklinikka.fi/">Koodiklinikka</a> where we recently had a thread about suggestions for books to read. I decided to collect them here for my own sake and for those who are seeking for something to read. Now, it's important to note that I have not read most of these so these are not my personal recommendations.</p><p>The prompt that started the discussion was, roughly translated, "Recommend me a good, modern scifi book or series with good theme, plot and characters."</p><h2 id="the-books">The books</h2><p><em>The books are ordered in alphabetical order (at least I tried, don't @ me if you find a mistake :D) by the last name of the author. Some are links to series page (if series was specifically mentioned), others are either first book in a series or individual books.</em></p><ul><li><a href="https://www.goodreads.com/series/59386-foundation-publication-order">Foundation series by Isaac Asimov</a></li><li><a href="https://www.goodreads.com/series/49118-culture">Culture series by Iain M. Banks</a></li><li><a href="https://www.goodreads.com/series/170872-wayfarers">Wayfarers series by Becky Chambers</a></li><li><a href="https://www.goodreads.com/book/show/43190272-to-be-taught-if-fortunate">To Be Taught, If Fortunate by Becky Chambers</a></li><li><a href="https://www.goodreads.com/en/book/show/41160292-exhalation">Exhalation by Ted Chiang</a></li><li><a href="https://www.goodreads.com/book/show/20518872-the-three-body-problem">The Three-Body Problem by Liu Cixin</a></li><li><a href="https://www.goodreads.com/series/56399-the-expanse">The Expanse by James S.A. Corey</a></li><li><a href="https://www.goodreads.com/book/show/27833670-dark-matter">Dark Matter by Blake Crouch</a></li><li><a href="https://www.goodreads.com/book/show/42046112-recursion">Recursion by Blkae Crouch</a></li><li><a href="https://www.goodreads.com/series/54576-valis-trilogy">VALIS trilogy by Philip K. Dick</a></li><li><a href="https://www.goodreads.com/series/81824-little-brother">Little Brother series by Cory Doctorow</a></li><li><a href="https://www.goodreads.com/book/show/24611819-the-peripheral">The Peripheral by William Gibson</a></li><li><a href="https://www.goodreads.com/book/show/57693437-last-exit">Last Exit by Max Gladstone</a></li><li><a href="https://www.goodreads.com/book/show/21611.The_Forever_War">The Forever War by Joe Haldeman</a></li><li><a href="https://www.goodreads.com/book/show/45258.Fallen_Dragon">Fallen Dragon by Peter F. Hamilton</a></li><li><a href="https://www.goodreads.com/book/show/44767458-dune">Dune by Frank Herbert</a></li><li><a href="https://www.goodreads.com/series/112296-the-broken-earth">The Broken Earth series by N.K. Jemisin</a></li><li><a href="https://www.goodreads.com/book/show/17333324-ancillary-justice">Ancillary Justice by Ann Leckie</a></li><li><a href="https://www.goodreads.com/book/show/26118426-ninefox-gambit">Ninefox Gambit by Yoon Ha Lee</a></li><li><a href="https://www.goodreads.com/book/show/37794149-a-memory-called-empire">A Memory Called Empire by Arkady Martine</a></li><li><a href="https://www.goodreads.com/series/182847-planetfall">Planetfall series by Emma Newman</a></li><li><a href="https://www.goodreads.com/series/166200-terra-ignota">Terra Ignota series by Ada Palmer</a></li><li><a href="https://www.goodreads.com/book/show/30748899-embers-of-war">Embers of War by Gareth L. Powell</a></li><li><a href="https://www.goodreads.com/book/show/7562764-the-quantum-thief">The Quantum Thief by Hannu Rajaniemi</a></li><li><a href="https://www.goodreads.com/book/show/9424053-blue-remembered-earth">Blue Remembered Earth (and the rest of Poseidon's Children series) by Alastair Reynolds</a></li><li><a href="https://www.goodreads.com/book/show/40048442-permafrost">Permafrost by Alastair Reynolds</a></li><li><a href="https://www.goodreads.com/book/show/89187.Revelation_Space">Revelation Space by Alastair Reynolds</a></li><li><a href="https://www.goodreads.com/book/show/28962452-revenger">Revenger by Alastair Reynolds</a></li><li><a href="https://www.goodreads.com/book/show/36510196-old-man-s-war">Old Man's War by John Scalzi</a></li><li><a href="https://www.goodreads.com/series/40461-hyperion-cantos">Hyperion Cantos series by Dan Simmons</a></li><li><a href="https://www.goodreads.com/en/book/show/222472.Halting_State">Halting State by Charles Stross</a></li><li><a href="https://www.goodreads.com/book/show/77711.A_Fire_Upon_the_Deep">A Fire Upon the Deep by Vernor Vinge</a></li><li><a href="https://www.goodreads.com/book/show/226004.A_Deepness_in_the_Sky">A Deepness in the Sky by Vernor Vinge</a></li><li><a href="https://www.goodreads.com/en/book/show/48484.Blindsight">Blindsight by Peter Watts</a></li><li><a href="https://www.goodreads.com/book/show/18007564-the-martian">The Martian by Andy Weir</a></li><li><a href="https://www.goodreads.com/book/show/54493401-project-hail-mary">Project Hail Mary by Andy Weir</a></li><li><a href="https://www.goodreads.com/series/191900-the-murderbot-diaries">The Murderbot Diaries series by Martha Wells</a></li></ul><p>Now the problem is, where to start. I decided to start with Chambers' Wayfarers series. I've only read a few chapters but I already like her style of character and world-building and am looking forward to learning more about the adventures.</p><h2 id="bonus-suggestion">Bonus Suggestion</h2><p>I also wanna recommend a great scifi short-story magazine that I've been subscribing for a while now. <a href="https://ethereamagazine.com/">Etherea Magazine</a> is monthly subscription based e-magazine that features short stories from Australian writers.</p>
Learning Rust #9: A talk about rustlings
2022-05-18T00:00:00Z
https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings/
<p>
<em>Last December I finally started learning Rust and in January I built and
published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my monthly blog series that is defnitely
not a tutorial but rather a place for me to keep track of my learning and
write about things I've learned along the way.</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community/">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>Learning Rust #9: A talk about rustlings (you're here)</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<h2 id="rust-finland-meetup-9-5-2022">Rust Finland meetup 9.5.2022</h2>
<h3 id="lightning-talk-rustlings">Lightning talk: rustlings</h3>
<p>
I got to host Rust Finland's first in-person meetup since the beginning of the
pandemic at Futurice on Monday May 9th and ended up doing a short lightning
talk about rustlings and even experiment a bit with live coding / ensemble
programming style with the audience.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/05/youtube-talk-rustlings.png" class="kg-image" alt="Youtube screenshot showing video "Lightning talk: rustlings, Juha-Matti Santala" from channel "Juhis Talks Tech"" />
</figure>
<p>
The talk can be found
<a href="https://www.youtube.com/watch?v=r0gQcSqZ3nI">on my Youtube channel at Juhis Talks Tech</a>
and is 8.5 minutes long.
</p>
<p>
I've also written about rustlings earlier in this series in
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
so if you prefer written stuff, you can find it there.
</p>
<h3 id="good-discussions">Good discussions</h3>
<p>
I also had really good discussions with people. I mentioned to one person my
plans at somepoint being to build a static site generator with Rust to see if
I could build a nice architecture for a plugin system. I abandoned it partly
due to lack of time but also because I couldn't figure out how to get started
with dynamically loading Rust plugins into a built binary.
</p>
<p>
I've mostly worked with Javascript or Python where it's quite straight-forward
to dynamically load code because it's just code being run and executed but the
idea of including plugin code on runtime while running the binary for the SSG
was something I couldn't grasp a good place to start.
</p>
<p>
I got some good tips for
<a href="https://doc.rust-lang.org/reference/linkage.html">dynamically loading libraries and linking them</a>
so I'll have to take a look at that, hopefully during the summer if I find
time to work on a new project (or more realistically, if this project will be
the one I prioritize my free time on as I have quite a few ideas on my
backlog).
</p>
A good job interview sets both sides up for success
2022-05-11T00:00:00Z
https://hamatti.org/posts/a-good-job-interview-sets-both-sides-up-for-success/
<p>Over the past few weeks I've ended up in a lot of discussions about job interviews in tech industry. I've talked with people doing interviews, I've talked about my experiences and opinions on interviews and I've talked with a lot of juniors, trying to give them some advice on how to best play the interview game. Because that's what it often unfortunately is.</p><p>One common thread popped up in many discussions that I feel very strongly about: <strong>I think a good job interview sets both up for success.</strong> I think that's something a lot of people can agree with so I wanna dig a bit deeper on what I mean by a good or a bad interview. Especially, I hate when <strong>a job interview is a game with hidden rules.</strong></p><h2 id="the-game-with-hidden-rules">The Game with Hidden Rules</h2><p>What do I then mean by these <strong>hidden rules</strong>? Any given job interview is relatively strapped with time: most interviews I've taken part have been between 30 and 60 minutes each. That means that if there's any given task, especially a technical one, given to the applicant, tradeoffs must happen. And when the interviewer keeps hidden which tradeoffs they want to see, we enter a realm where job interviews become minefields of traps and quite frankly, it's unlikely you find the best people.</p><p>Let's say there's a technical task in an interview to implement an LRU cache. An interview situation is not real life production development, there's even more tradeoffs to be made than usual: should you write tests, how extensively should you document it, what assumptions can you make, how much optimization should be done and so on.</p><p>Now the response to that is often: <em>I want a good senior to ask these questions and have a discussion.</em> And that's great if it is. But given that interview is kinda like a game, it might be that another interviewer is more interested in your technical implementation skills and as an applicant, <strong>you need to make a guess which one your interviewer is.</strong> </p><p>I think those kind of guesses should not have to happen in an interview. For example, you could on the other hand end up discussing those questions with your interviewer for a good chunk of time, eventually running out of time to actually implement things and depending on what the hidden rules were, you might be disqualified because you spend too much time on talking.</p><p>And that's why I'm so passionately against putting the candidate into a position where they need to guess which kind of questions and which kind of answers the other party is looking for.</p><h2 id="another-look-into-the-same-problem">Another look into the same problem</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/05/math-orchestra.jpeg" class="kg-image" alt="Question from a math test: “An orchestra of 120 players takes 40 minutes to play Beethoven’s 9th Symphony. How long would it take for 60 players to play the symphony?”" /></figure><p>I saw <a href="https://twitter.com/vorahsa/status/1520395547009556481">this picture on Twitter the other day</a> with accompanying text </p><blockquote>"This is an excellent question and the number of supposedly smart people thinking it’s intended as a “plug numbers in formula” question makes me very sad"</blockquote><p>I think this is exactly the type of situation that the hidden rules in job interviews are too. I do agree that it's important when learning mathematics to also understand that once you have a hammer, things might look like nails but you need to be careful.</p><p>But if the context of that is a math exam, I'd argue it's a really bad question. First, because during an exam we're in an artificial environment where expectations are that we are testing math skills. I've seen so many times when these kind of math questions with written stories are so inaccurate and maybe the person who wrote it knew less or from different culture than the one reading and solving it.</p><p>It might be that the "correct" answer to question above is 160 minutes. It's very possible that being a "smart ass" and pointing out that in real life, adding or removing players doesn't affect the length of the play could disqualify you.</p><p>I still remember in high school when a friend of mine was deducted points in a math question because he pointed out that a 0-minute taxi drive does not cost 0 euros because there's a starting cost for every ride. The teacher didn't agree with that, claiming that real-life circumstances were not part of the question and should not be brought into the equation.</p><p>Of course, it's just an anecdote but that's kinda the point. When we do math exams or job interviews, we're few degrees separated from the real world because they are artificially simplified situations.</p><h2 id="a-good-interview-sets-up-for-success">A good interview sets up for success</h2><p>I firmly believe that setting up trick questions to see where the other party fails because they didn't think the same simplifications/abstractions as you did as an interviewer, are not beneficial to anyone.</p><p>A good interview sets up expectations and frames up the situation better so that the discussion can focus on figuring out if the skills of the candidate match the needs of the company and role as well as figuring out if what the company is looking for and can offer, matches what the candidate is looking for.</p><p>Making your candidate guess what the parameters are, is not a good or necessary way to assess skill level. And if they end up asking about all of those trying to discover the parameters, it's likely they won't have time to actually code and the interviewer might come to the conclusion that the candidate only talks tech but can't code or isn't focused enough on deliverables.</p><p>None of this means that you can't ask difficult questions or try to find the boundaries of candidate's skills. You absolutely can and most cases should. But playing tricks is kinda like randomly throwing out 50% of the applications and say "it's unlucky to be discarded randomly and I don't want people who are unlucky". It's not based on anything real and it does not set up for a good relationship either.</p><p>In general, job interviews should be about finding good matches – or invalidating those assumptions – so that both sides can move on, either together or separately. </p><h2 id="my-best-interview-experiences">My best interview experiences</h2><p>There are a small handful of really good interview experiences I've had as a candidate – some that lead to a job and others that did not. The common thing in all of those has been that we've both been excited about the topic and ended up having great discussions. Less exam-style questions and answers but deep discussions about developer relations, community building and technology. I also believe they can be more effective because it's harder to "cheat" or practise answers up front when you end up having a proper discussion. </p><p>In one interview for a developer advocate role a few years ago, me and their current developer advocate just ended up geeking out and chatting about why developer advocacy is so cool for an hour or so. I don't think a single interview question was asked during that time. I ended up also making a new friend in the industry despite eventually not continuing the process with them.</p><p>In another more recent one, we started in a rather interview-y style but quickly we realized that we share a lot of thoughts and ideas but also had good disagreements on certain topics and that led into a discussions that got me really excited (and honestly, I really wanted then to work or collaborate with that person one day).</p><p>One interview I finished by recommending a few people I knew in the industry because I realized it wasn't a great match for what I was looking for at the time. Thus we mostly just talked about how to approach developer relations for a company like theirs and it turned out to become more of a brainstorming session between professionals than an interview.</p><p>A developer candidate might not remember some Javascript standard library implementation details off the top of their head in a nervous interview situation (I'm horrible at these kind of tasks) but if you can have a in-depth discussion about a project they did and tech choices they made and how they would change the architecture or make different choices and why, I'm convinced you get much better insights into their skills as a software developer.</p><p>Not related to an interview but this Monday I had a great discussion in a Rust meetup about how I had planned to start building a static site generator with Rust as a learning project but ended up not figuring out how to architecture the plugin system because I'm so used to dynamic languages where I could just run any code from plugin library and less used to compiled software as is the case with Rust. </p><p>Now, that discussion was a great one and I think even though it wasn't the goal of the discussion, the person I was chatting with probably got quite a good idea of where my strengths and skill limitations with the specific language but also with software development in general are. Had that been an interview discussion, I would have been really happy about the outcome. Not because I succeeded to wow someone with my impeccable Rust skills but because I felt that this discussion let me frame my skills and understanding in a very realistic way – without having to guess what my skills are on a scale from 1 to 10.</p><h2 id="interviewing-is-hard">Interviewing is hard</h2><p>I know recruitment and interviewing is hard. Building an internal system that gives different people an equal opportunity without becoming a rigorous exam quiz is hard. But that's probably why the good ones really stand out.</p><p>In interview is a two-way street. As much as it exists for the company to assess the skills of a candidate, it's also a place where the candidate has an opportunity to assess the company for a good fit and opportunity. And for me, a good interview has always been a really strong selling point – or a red flag in some cases.</p>
A week in life of a developer advocate
2022-05-04T00:00:00Z
https://hamatti.org/posts/a-week-in-life-of-a-developer-advocate/
<p>
Developer Advocacy and its umbrella term Developer Relations is a relatively
new field. Sure, some people have been doing it for a long time (at some point
Evangelist was another term used) and some aspects of DevRel have been roles
for decades (like technical writing) but the boom is very current.
</p>
<p>
I've been working officially as a developer advocate for the past 3 years but
I have a longer history of building communities, teaching programming and
being a developer so it's hard to say when I actually started.
</p>
<p>
Every now and then, people ask me:
<em>"So what is it that you actually do?" </em>and because real life is
complex and messy, I usually answer something vague and almost funny like
<em>"All things dev but dev itself" </em>or
<em>"Everything that's forbidden in a pandemic". </em>Maybe it's because it's
hard to start listing all the things I actually do because they are so varied.
</p>
<p>
I decided to keep a journal for one week to try to document my week as a
developer advocate. Not every week is like this but it's quite a nice
representation of my work and its many faces. And by work, I mean all the
stuff I do with communities as in addition to being a Developer Advocate at
Futurice, I also build and run different developer communities. So not
everything on this journal is strictly Futurice stuff. I just find it
impossible to separate Developer Advocate me from the other Developer Advocate
me. They are so similar.
</p>
<p>
I wrote this blog post one day at the time at the end of each day so there's
probably a lot of inconsistent use of past, present and future tenses.
</p>
<h2 id="monday-25-4-2022">Monday, 25.4.2022</h2>
<p>
We start the week with Monday. I didn't have a super relaxing weekend (to be
honest, haven't had one since the pandemic started) so I was a bit grumpy and
tired already when the week started. Once you see what this week has to offer,
you can understand why it was a tiny bit concerning and exhausting to start
with.
</p>
<h3 id="being-a-futubuddy">Being a FutuBuddy</h3>
<p>
This Monday we got some new collaegues at
<a href="https://futurice.com/">Futurice</a> and I had promised to be a
FutuBuddy for one developer who was returning to the company. Our FutuBuddy
system is something I'm really excited for and I've had such good experiences
being one. Basically it means that in addition to the regular onboarding by
different functions at the company, everyone is assigned a buddy – another
employee who's been at the company for a while and who can help out navigate
all the nuances and unwritten cultures and so on.
</p>
<p>
Despite having a wild busy week, I promised to be a FutuBuddy since my buddy
had already worked with us before so I knew there was a bit less buddying
needed. And we knew each other well enough that I trusted him to raise any
issues during the week if need arose.
</p>
<p>
We did start the week with a lunch with supervisors, buddies and new joiners –
one of my favorite ways to kick off a new week and to meet new people.
</p>
<h3 id="lots-of-small-things-during-the-day">
Lots of small things during the day
</h3>
<p>
Every day I have a ton of small stuff that comes up to my table. Here are some
that I did today:
</p>
<ul>
<li>Sent internal messages about our events for this week.</li>
<li>
I needed to alter my food order for tonight's event (and I hate phone calls)
so I had to call the restaurant to ask if they could postpone the delivery
with two hours. There had been a miscommunication between me and the meetup
organizers and I learned this day about it.
</li>
<li>
I helped a colleague out with an internal event we are hosting in summer.
</li>
<li>
One of my Monday routines is to go through our internal Tech Weeklies (see
more under Friday heading) talks: this week we only had one slot booked (by
me) so I posted on Slack asking if anyone could pick up a slot and talk
about a project they are working at. This time I succeeded so I did a bit of
comms to sync about that and wrote emails and Slack comms, scheduled for
Wednesday.
</li>
<li>
I had an impromptu meeting with recruitment about our new SRE team
recruitment, went through what relevant events and other community efforts
we have and agreed to book the next developer newsletter month focused on
SRE/Cloud/DevOps stuff.
</li>
<li>
I'm working with our summer employee recruitment (ads, comms, being with the
summer workers during summer) and one aspect of that is that we do weekly
smileys. Smileys is a session where we come together once a week and share
how we feel. To make it a great remote experience, I took an open source
project developed by my colleagues and worked with them to set it up for us
and wrote docs for summer on how to use it.
</li>
<li>
I also ended up doing a small HTML/CSS improvement on the site to improve
its accessibility, made a pull request and submitted it to the team.
</li>
<li>
I synced with a local Python meetup and booked a date for their meetup to be
hosted at our office in late May.
</li>
<li>
I said "I'll get back to you tomorrow" to a couple of colleagues because I
ran out of time to help with their requests.
</li>
<li>
I synced with our family company Columbia Road about Tuesday event, when
they should arrive and what is expected from them.
</li>
<li>
I had a lovely chat with colleagues about the 1st of May celebration culture
in Helsinki.
</li>
<li>I went to a local restaurant to order food for Tuesday's event.</li>
<li>I ordered drinks for 3 upcoming events.</li>
<li>I posted a few Star Wars memes to company Slack.</li>
<li>
I connected two community members since they had interests in the same open
source project.
</li>
<li>
I booked a catchup meeting for Tuesday with a colleague I had great
discussions pre-pandemic but somehow lost connection with during the
pandemic.
</li>
<li>
I booked a hotel for Wed-Thu night as I'll be heading out of town for a
meetup & a lunch with local devs.
</li>
</ul>
<h3 id="azure-friends-meetup-at-the-office">
Azure & Friends meetup at the office
</h3>
<p>
This Monday we also hosted a meetup for a relatively new developer community
<a href="https://www.azureandfriends.com/">Azure & Friends</a> as they are
starting their operations to build a welcoming community around Azure
development.
</p>
<p>
Before this week, this meant figuring out a date, trying to help find
speakers, ordering food and drinks, doing a couple of AV checks to make sure
we can livestream from our office setup to their Youtube account and so on.
</p>
<p>
During today, I got to do the things I love: carried chairs back and forth
setting up the office, put drinks into the fridge, set up the livestream, made
sure the mics are all charged, made a few slides to put on before the event
(for internal comms) and during breaks (for Youtube).
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/04/teemu-azure-meetup.jpeg" class="kg-image" alt="Teemu speaking in front of a small audience" />
</figure>
<p>
Then welcoming people in as they arrive, helping speakers set up with mics and
laptops, taking out drinks from the fridge, doing a lot of small talk before
the event, making sure everyone's having a good time and so on. The regular
event stuff. Took a few photos, posted some tweets, monitored the livestream,
switched between layouts for the stream, looked after audio levels for the
event and so on.
</p>
<p>
After the official program and talks, we ended up staying at the office for a
few hours to chat about life, software development, cloud stuff, sharing
stories from projects we've done and so on. Eating a lot of pizza because I
had ordered a bit too much. Laughed a lot. In general, had a really good time.
</p>
<p>
Once people left bit after 9pm, I cleaned up the office, did a few follow-up
posts to the community Slack, put on the alarms and headed home.
</p>
<p>
Before heading to bed, I wrote a bit of advice or ideas for a former colleague
looking to hire their first developer advocate.
</p>
<p>
Quite a Monday. And it was just the first of all the days to come this week.
</p>
<h2 id="tuesday-26-4-2022">Tuesday, 26.4.2022</h2>
<p>
To kick off Tuesday, I started with the thing I had postponed from the day
before: helping a colleague in Germany to plan an off-site for their
developers. We brainstormed a few ideas which I've used in the past for team
bonding exercises and having a good time at an offsite.
</p>
<p>
I also did some follow up from the previous night with a few people I had met
there and started to get ready for another event day.
</p>
<p>
I had an async Slack meeting with one colleague who'll be the contributor to
our May issue of Dev Breakfast and I was excited to get them aboard to share
their SRE and Cloud related topics to our readers. My earlier efforts in
documentation paid off too because I had some guidelines and clear
expectations written down in our intra so I was able to just pass along those
and he could get started.
</p>
<p>
During the commute, I ideated a meetup to host for last day of May with
<a href="https://react-finland.fi/">React Finland</a> and started booking the
venue at our office and got the first two speakers for the meetup as well. The
conference itself is in mid-September but we wanted to bring the community
together once more before the summer. A few messages here and there and we had
quite a nice base structure for the event together.
</p>
<p>
Once I got to the office, I had a few chats with a colleague who's visiting us
from our Munich office, ate some lunch and then had a call with our marketing
person who's responsible especially with our junior recruitment marketing. We
went through what my needs at student and university collaboration team are
and how we could best improve our common processes for the next fall when the
next wave of events with students start again. Super nice to have more people
working on it so we can be more effective with the reach and more prepared to
build lasting impact.
</p>
<p>
Time for some emails! Found a designer to meet some students who wanted to
learn more about our company and real work life of a designer and I got the
best possible person to chat with them, I'm so excited about that. Few follow
ups on discussions and trying to find a best time to do a call to learn more
about social impact and how we can get involved with the organization. More
meetup prep for upcoming events.
</p>
<p>
Since I'm the one hosting Wednesday's meetup for my community instead of being
the sponsor/host, I sent messages to our speakers to make sure everything's
okay and double checking if they need anything. Luckily, everything's good and
we're gonna have an amazing meetup. And I booked train tickets to Turku once I
was able to confirm which train I'm needed in.
</p>
<p>
I'll be doing a talk this Friday in our internal Tech Weeklies so I spent a
bit of time today to try to figure out the structure and most important points
I want to bring up. The talk will be about the 10 years of Tech Weeklies (so
kinda a meta talk) and in addition to a short history tour, aims to answer the
question: why it's worthwhile for companies to run these, for developers to do
talks and for consultants to participate in them. If everything goes well,
we'll have that in Youtube soon.
</p>
<p>
We're also moving into a new office over the summer so I had to make sure when
is the last day that I can organize events here. I originally tried to keep
all the events in May but today some might start slipping into June so had to
find a hard deadline.
</p>
<p>
Just before the event, I also had a chance to do a 30 minute call with a
colleague with whom we talked and sparred each other a lot before the pandemic
but haven't talked in like two years. We've both gone through some hard times
during the pandemic and it was so lovely to just chat about it and spark a bit
of hope into the life.
</p>
<h3 id="student-event-with-hive-helsinki">Student event with Hive Helsinki</h3>
<p>
The main event of Tuesday was a long awaited student event with students from
<a href="https://www.hive.fi/en/">Hive Helsinki</a>. It's been two years in
the making with multiple cancellations due to the pandemic but finally we got
the students here. I'm personally such a huge fan of the school and their
concept and it was so great to spend the evening with them.
</p>
<p>
Like in Monday, event day means a lot of practical stuff: picking up food,
putting drinks to cool, moving chairs around, setting up presentations,
syncing up with other people involved from our side and doing internal comms
about what's going to happen.
</p>
<p>
This time I had the privilege to be on the stage, first to talk about our
company and what it's like to actually work here (and share some of my
favorite anecdotes like a story about our Han Solo in carbonite ice that we
have in the office) and then do a main talk of the evening that I had heard
the students had awaited for a while. I talked about how to build hobby
projects and use those to the best of their capability when applying for jobs
and how to talk about those projects.
</p>
<p>
After the talks, we ended up staying at the office for over three more hours
just chatting, playing pool, eating, playing Mario Kart and so on. Just like
in meetups and conferences, this part is where the real impact happens: casual
discussions where people can meet new people, have discussions about tech and
work life (and ice hockey as the Finnish Liiga finals were on-going at the
same time) with each other and with people already in the industry.
</p>
<p>
And once the happy times are over and guests have left, it's time to clean up
the office, move chairs back, clean up, do some follow ups internally and with
the school people and send a few last emails before heading home.
</p>
<p>
Once again there were a few things I had to postpone for tomorrow to get some
answers from third parties and because I ended up running out of the day
again. I don't normally work this long days but this week I'm putting in a lot
of hours because a lot is happening and then next week I'll chill when there's
less events. I really enjoy the flexibility that this job brings with it.
</p>
<p>One thing is for sure, I desperately need a good massage after this week.</p>
<h2 id="wednesday-27-4-2022">Wednesday, 27.4.2022</h2>
<p>Somehow, Wednesday was the "chill" day of the week.</p>
<p>
Let's list the "small" things again because tonight (I'm writing these at the
end of each day) my prose writing energy is low.
</p>
<ul>
<li>
Follow ups from yesterday's event: got some feedback from the visitors,
checked how many people had signed up for a newsletter (spoilers: many),
heard that we got some job applications already in from Tuesday's visitors
so a really really good day. Got positive feedback also from colleagues who
listened to my talks. Those truly made my day.
</li>
<li>
Speaking of feedback, there's very rarely too much positive feedback given
and especially for all the "hidden" work that people do that is taken for
granted and that otherwise goes unnoticed. I'm so happy that at Futurice, we
have a concept called Jolt where we give publicly thanks and positive
feedback to colleagues when they do a great job or help out with something.
Today I got to share some Jolt love for our amazing office specialist. I'll
need to write about our Jolt culture one day, it's amazing.
</li>
<li>
I briefly discussed our student and university collaboration plans and
guidelines with a new colleague who was interested in learning what we do in
that realm and if we could partner up with some new ones.
</li>
<li>
Made some more plans for our junior recruitment marketing for summer with
our marketing people.
</li>
<li>
Wrote some background materials for a colleague from our recruitment team
for her meeting next week as someone was interested in our approach with
junior talent and why it's working so well.
</li>
<li>
Booked a few meetings to collaborate on how to build communities both
internally and externally.
</li>
<li>
Got invited to speak in an event next month and came up with a new talk
title and rough sketch for what I'll be talking about. More of that later
when the event gets published.
</li>
<li>
Booked a few new events for May, the month is starting to look amazing
regarding events.
</li>
<li>
Since it's Wednesday, I published a new blog post, this time it was
<a href="https://hamatti.org/posts/shy-introverts-short-guide-to-speaking-in-conferences/">Shy introvert's (short) guide to speaking in conferences</a>. I usually publish them in the morning but I only realized it's Wednesday
in the afternoon. If you wanna know why it's not automated,
<a href="https://www.youtube.com/watch?v=vd22qJQnY30">see this talk about my website project</a>
(spoilers: it's bit of a mess but it's my mess ❤️)
</li>
<li>
After publishing the blog post, a few people reached out so had discussions
with them and was happy to hear how their careers had progressed since our
previous discussions.
</li>
<li>
Django Day Copenhagen 2022 videos were published today! You can find them
all at
<a href="https://www.youtube.com/playlist?list=PLEpW1LzVyQWgq9fFj4hobumxnszdMDWGY">Django Danmark's Youtube channel</a>
and you can
<a href="https://hamatti.org/posts/my-trip-to-django-day-copenhagen-2022-and-stockholm/">read my recommendations from my blog post earlier</a>.
</li>
<li>Time to hit the road, it's travel day!</li>
</ul>
<h3 id="turku-3-frontend-meetup">Turku <3 Frontend meetup</h3>
<p>
Day 3 of the week means third developer event. I took the train to Turku (~2hr
ride) for our
<a href="http://turkufrontend.fi/">Turku <3 Frontend</a> community's
meetup. I started the meetup in December of 2015 and we've had monthly meetups
since (except during the pandemic) and now we finally kicked off this spring
with a super awesome meetup.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/04/eevis-turkufrontend.jpeg" class="kg-image" alt="Eevis speaking in front of meetup audience with a slide that says "Let's keep our promises"" />
</figure>
<p>
A friend and an old colleague of mine,
<a href="https://eevis.codes/">Eevis</a> did
<a href="https://eevis.codes/talks/lets-keep-our-promises/">a fantastic talk about keyboard navigation and ARIA</a>. I've heard her talk many times about accessibility and every time it's spot
on and very educational. Second talk of the evening was by another friend
Konsta who did a brilliant presentation about Next.js. Combining slides with
GitHub Copilot driven live demos was a really good approach. It's so
interesting to see how much Copilot can change the live demo scene in
presentations when you have to remember so much less for simple demos.
</p>
<p>
In addition to talks, the best part of the meetup is to catch up with the
community before, during and after the meetup. After the talks we headed to a
local pub and I think we had the biggest afterbeers crowd ever in the 6.5
years of running this meetup – we even got a few people who weren't part of
the meetup to join for drinks. Whenever I can, I try to book a hotel for the
night after the meetup so I don't have to run to the train but can stay and
have discussions with everyone.
</p>
<p>
Out of all the communities I'm involved with and love, this one is by far my
most favorite one. Building it and growing it is my proudest achievement and
the first thing that was properly started by me (with friends) and not just
someone else's project I continued. I've made so many dozens of amazing
friends through that community and while everything else can be a bit chaotic
time to time, coming to Turku <3 Frontend's meetup and hanging out with
friends, everything feels just right.
</p>
<p>
After the beers, I headed to the hotel, grabbed some kebab on the way and hit
the bed, ready for the fourth event day of the week.
</p>
<h2 id="thursday-28-4-2022">Thursday, 28.4.2022</h2>
<blockquote>
“This must be Thursday,' said Arthur to himself, sinking low over his beer. 'I
never could get the hang of Thursdays.”, Hitchhiker's Guide to the Galaxy by
Douglas Adams
</blockquote>
<p>
Above quote is my favorite quote about any day of the week. Luckily this time,
the sentiment towards this particular Thursday is very different: it's gonna
be a great day.
</p>
<p>
For today's event, we'll add some designers and business people into the mix
as I'll be an alumni panelist with
<a href="https://startuplifers.org/">Startuplifers</a>' Startup your career
event in the evening in Otaniemi.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/04/burger.jpeg" class="kg-image" alt="A delicious chicken burger with fries and ketchup on the side" />
</figure>
<p>But before evening event, great things to do:</p>
<ul>
<li>
Woke up to a message inviting me to join a startup accelerator as a coach
this summer – easy and enthusiastic <strong>yes. </strong>I used to work at
Boost Turku building startup community and running the accelerator in
2015–2017 and after that I've been coaching in various startup incubators,
accelerators and events helping new entrepreneurs figure out how to build
minimum viable products, communities or master the skill of pitching.
</li>
<li>
Koodia pinnan alla (a Finnish podcast about backend development) published
<a href="https://koodiapinnanalla.fi/episodes/17-tekoaly-peleissa">their new episode about AI development in games</a>
so while doing morning work, I listened to Ykä, Markus and Antti talk about
that.
</li>
<li>
This week's Friday is the last Friday of the month so we'll be publishing
April Issue of Dev Breakfast newsletter. That means today was the time to
make sure everything is in order. Our marketing team puts the newsletter
together and I do a quick run through of the preview to add a few extra eyes
to make sure everything looks good. I'm so glad I get to work with our
newsletter team that always delivers great stuff.
</li>
<li>
Since I was in Turku for the morning, I booked a lunch with a friend from
our community. We talked for a few hours about meetups and communities,
speaking in events, podcasts and started planning for a new meetup that will
hopefully become reality in the fall. I also connected him to a few podcast
hosts as he'd be a great podcast guest.
</li>
<li>
After lunch, I went through the panel discussion questions to prepare for
the event. I've done the same panel discussion many times so didn't have to
spend too much time for preparing.
</li>
<li>
On Friday, I'd be speaking at
<a href="https://futurice.com/techweeklies">Tech Weeklies</a> so I spent the
2hr train ride making the slides and coming up with the talk. For me, making
a talk is a very creative process and more than me making the presentation,
I try to discover the presentation from somewhere in my subconcious so I
often play around with ideas, fonts, images attempting to spark my
imagination and when I get that "Heureka!" feeling, I build rest of the talk
around that.
</li>
<li>
Like pretty much every day all year long, I got some emails about potential
collaborations and partnerships, went through them, answered those and
forwarded others to people more suitable to handle them.
</li>
<li>
Brainstormed a bit with our accessibility specialists at work if we could do
some community sessions for
<a href="https://accessibility.day/">Global Accessibility Awareness Day</a>
next month.
</li>
</ul>
<h3 id="startup-your-career-event">Startup Your Career event</h3>
<p>
4th day of the week, 4th event of the week. I met an old friend and fellow
panelist for the event at the subway station and we got talking, catching up
since we hadn't seen since the beginning of the pandemic. It was so nice to
share the stage with her today. I also learned she's gonna organize an
internal hackathon at work and you know, hackathons are my jam, so I got all
excited about why I think they are great for any company.
</p>
<p>
<a href="https://startuplifers.org/">Startuplifers</a> is a student-run
organization from Aalto University that helps top tech, design and business
students from Finland to get internships in Silicon Valley. I spent my year in
2014 (yikes, 8 years ago, I'm growing old!) at Chartio and it was the most
life-changing event in my life. There's me before that year and me after that
year and those two are completely different lifes. I've said many times that
everything I've done since 2015 has been thanks to that experience, growth,
gain in self-confidence and the networks I made there.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/04/startuplifers-april-2022.jpeg" class="kg-image" alt="Four people on stage sitting in chairs during panel discussion" />
</figure>
<p>
We had a great panel discussion with my great fellow alumni Satu and Vilma. An
hour just flew by as we went down the memory lane and gave our advice to the
current students about how to stand out from the crowd when applying for jobs
and interviewing in Silicon Valley. It was so cool to see once again a bunch
of people in the audience that I knew from various communities – some even
heard me talk for the second time this week.
</p>
<p>
It was so nice to see fellow alumni, make new friends, and help out people
looking to get into the industry or to take the next step. I think I managed
to introduce the concept of meetups to a few new students and I'm excited to
meet them later this spring in some of the various tech meetups in Helsinki as
well as in the <a href="https://koodiklinikka.fi/">Koodiklinikka</a> Slack.
</p>
<p>
Back in the days when I was a student, I didn't really have any connections
with more experienced developers in the industry so now that I'm here and I
have my wide networks, I try to bring them closer to the students as much as
possible. I always dream about starting a good mentoring network to help with
that but it's still an idea waiting for the right context and collaboration.
One day I'll make it happen, I'm sure.
</p>
<h2 id="friday-29-4-2022">Friday, 29.4.2022</h2>
<p>
We've reached the end of the work week but there's still Friday to go. This
week my Friday is way more chill than the other days of this week.
</p>
<p>
My morning was mostly occupied by a case in one of the communities I'm
involved with that required coordination between our admins and doing a bit of
public commenting that I can't go deeper into. Unfortunately cases like these
pop up in communities every now and then and I always wish there's a way to
find a win-win situation for everyone involved.
</p>
<h3 id="dev-breakfast-publish-date">Dev Breakfast publish date</h3>
<p>
As I've mentioned a few times in this blog post, one thing I run is our
monthly developer newsletter Dev Breakfast. It gets sent to our subscribers
every last Friday of the month at breakfast time (CET/EEST timezones) and this
Friday was no exception to that.
</p>
<p>
My colleague <a href="https://twitter.com/notknut">Knut</a> from our Berlin
office was the contributor this month and he put together a fantastic
newsletter with great articles. If you're not a subscriber yet, you can find
the
<a href="https://hello.futurice.com/dev-breakfast-april-2022">April newsletter here</a>. I really enjoyed the article he shared about
<a href="https://medium.com/freely-sharing-the-sum-of-all-knowledge/writing-a-wikidata-query-discovering-women-writers-from-north-africa-d020634f0f6c">Wikidata Queries</a>.
</p>
<p>
For me, there's not that much to do when the newsletter comes out. I write a
post to celebrate it and to give shoutouts to our contributors in our company
Slack and to encourage others who want to join as contributors to reach out. I
also share it on social media.
</p>
<h3 id="tech-weeklies-10-year-anniversary">
Tech Weeklies – 10 year anniversary
</h3>
<p>
My main thing for today was to host our 10 year anniversary
<a href="https://futurice.com/techweeklies">Tech Weeklies</a>. One of our
developers shared a demo of a feature they had designed and built in one of
our projects and it was so cool to learn about a new Python tool that I had
never heard about before (it was
<a href="https://github.com/aws/chalice">Chalice</a> for those interested).
</p>
<p>
I had prepared also a presentation that looked into the history and the past
10 years of Tech Weeklies and spent most of my presentation answering the
question
<em>"Why should a tech company invest money and time in internal tech sharing
sessions?". </em>I looked at it using one of our decision making tools at Futurice called 4x2
(<a href="https://futurice.com/blog/decision-making-at-futurice">there's an older blog post about it when it was called 3x2 in Futurice
blog</a>).
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/04/juhis-tech-weeklies-april-2022.jpg" class="kg-image" alt="Me on a stage in front of small developer audience, wearing a headset and "It depends" hoodie. A slide behind me says "10 years of Futurice Tech Weeklies" with a few party emojis." />
</figure>
<p>
I made a case for why it's valuable for our people (developers as devs, as
consultants and as speakers), our business (more skilled and up-to-date devs,
employee retention, recruitment marketing), our clients (getting access to our
full community of developers who know each other, what each other are experts
on and who keep each other up to date) and the world.
</p>
<p>
Unfortunately though, the Google Meet had a noise cancellation filter on
(first time it's happened to me in 4 years here) so the algorithm decided that
my speech is not proper speech and it filtered me out, rendering the talk
impossible to listen to most people who joined the event and the recording
that I had planned to share to our leadership and to Youtube.
</p>
<p>
Despite the problems, there were a bunch of people in the office who did hear
the talk and at least one of them got encouraged to maybe do a talk, so that's
a success.
</p>
<p>
The problems did lead to me wanting to curl up and cry. Not because of the
missed recording, that sucked too, but because these noise cancellation
filters are the one thing in life that make me feel so little, so broken, so
incompatible and so not belonging. It's like a stake pushed through my heart
and soul. It's awkward and embarrassing to me every time it happens and the
worst is that since I can't hear it myself, I don't usually know about it
until it's too late.
</p>
<p>
That was bit of a bummer and ruined this Friday's 1st of May/Labour Day/Wappu
celebration for me.
</p>
<h2 id="that-s-a-wrap">That's a wrap</h2>
<p>
What a week, huh? This week was in a way very well descriptive of my job and
especially the variety in it: everything from carrying chairs and interactions
with the members of the community in events to planning our operations on a
strategic level with multiple stakeholders within the company and with
external partners.
</p>
<p>
But also it's not always this busy, this week was definitely an exception with
5 nights in a row. Usually I have 2-3 events a week with one or two of them
having me on a stage to present something. The amount of everything else was
very descriptive though.
</p>
<p>
I had so much fun this week but also worked way too many hours. I'll be taking
those hours back next week when I'm taking it easier as I only have one
1.5-day event with a bit of traveling (I'll be mentoring early stage startups
and young entrepreneurs
<a href="https://shipfestival.org/">at *ship festival</a>) and a few mentoring
sessions and brainstorming sessions for future events and collaborations.
</p>
<p>
If anyone I met during this week is reading, I'm very happy we met – whether
it was for the first time or if we've known each other for years. Doing this
with all the communities I get to be involved with is what makes me enjoy life
so much.
</p>
<p>
The weekend was mostly spent on resting and spending quality time with family
celebrating
<a href="https://en.wikipedia.org/wiki/Walpurgis_Night#Finland">Vappu</a>. I
did do a few community things though since I had 2 hours to spend in a train:
I set up a date for the next Helsinki Dev Lunch for 19.5 and shared that in
social media and Slack communities, pondered a bit about my upcoming talk
abstract I promised to send next week and continued making some plans for one
new meetup concept (about documentation!) and for a new one-off event I really
want to organize but haven't found the right combination of things yet to make
it happen. And I felt a bit of FOMO that my friends are going to
<a href="https://jsheroes.io/">JSHeroes</a> in June and I'm not.
</p>
Shy introvert's (short) guide to speaking in conferences
2022-04-27T00:00:00Z
https://hamatti.org/posts/shy-introverts-short-guide-to-speaking-in-conferences/
<p>
I'll let you in on a personal secret: every time I go to speak in an event, I
get so nervous. Especially the last 10-15 minutes before my turn to take the
stage, I'm terrified.
</p>
<p>
Under all the learned small talk and nervousness-hiding skills, deep down I'm
very shy and introverted and find it difficult to talk to strangers. Somehow,
despite all that, I've ended up in a role both at work and at hobbies where I
meet with hundreds of people every month and speak to audiences on a stage
weekly. So I had to learn how to make it bit easier to meet people and get on
stage on conferences.
</p>
<p>
As I was originally writing this, I was sitting in the railway station of
Stockholm on the way to
<a href="https://2022.djangoday.dk/">Django Day Copenhagen</a> to speak. That
means it took me few weeks to finish the post after the conference.
</p>
<h2 id="tip-1-get-to-know-people-before-event">
Tip 1: Get to know people before event
</h2>
<p>
One thing I love to do during the weeks and days leading into the conference
is to be active to chat with fellow participants and speakers. Often
conferences have chat platforms like Slack/Discord/Zulip/Telegram that you can
join. I'm very comfortable chatting with people in written form so I tend to
be active in those before the event. When I was travelling to
<strong>Pycon CZ</strong> in 2019, I got to know a lot of people so when I
eventually arrived to Ostrava, a fellow speaker came to talk to me on the
street as I was walking to my hotel. That was awesome.
</p>
<p>
Right now on my way to <strong>Django Day</strong>, I've been posting pictures
and updates from my travel to the event: from the Helsinki-Stockholm ferry,
from the Stockholm station and so on. And it has encouraged others to do the
same. I think it brings a lovely vibe to the community when you see others
make their way to the event city.
</p>
<p>
If there's no such chat platform offered, look up fellow speakers from Twitter
and reach out.
</p>
<p>
I also love to set up a dinner for the night before if one is not organized by
the event. Sometimes it's just 2 people who show up, sometimes it's more.
Regardless, it's a great opportunity to make a few more friends leading up to
the event.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/04/djangoday-speaker-dinner.jpeg" class="kg-image" alt="Five people sitting around a round table in a restaurant, waiting for food to arrive, all smiling and looking into the camera." />
</figure>
<p>
Before Django Day Copenhagen, we got together with these four fellow speakers
and became friends the night before the event. It made it so much easier to go
to the conference the other day when I already knew some people!
</p>
<h2 id="tip-2-get-to-know-people-at-the-event-before-your-talk">
Tip 2: Get to know people at the event before your talk
</h2>
<p>
The time leading up to your talk is nervous and many people like to stay away
from people to practice. What I like to do is spend my conference day making
friends. Before the talks, during breakfast, during breaks and lunch, I try to
find people to talk with. Especially great if I can find groups with some
people I made friends with before the event that I can ease my way into the
discussion through someone I know.
</p>
<p>
This advice is easy to write but hard to execute for a shy introvert.
Sometimes I succeed, sometimes the anxiety gets the best me and I end up not
doing it.
</p>
<p>
One thing I did in <strong>PyCon Sweden</strong> in 2019 was that as the first
day was coming to a close, I walked around the venue inviting random people
for a dinner and we ended up with a group of 8 and had lovely dinner getting
to know each other.
</p>
<p>
Both of these lead towards one goal:
<strong>instead of speaking to strangers, by the time my talk starts, I'm talking
to friends. </strong>And speaking to friends is so much easier than to strangers.
</p>
<h2 id="tip-3-do-a-practice-run-in-a-local-meetup">
Tip 3: Do a practice run in a local meetup
</h2>
<p>
No matter if you're a first-time speaker or an experienced speaker with a new
talk, practice makes perfect. Local meetups, user groups and lunch'n'learn
sessions at work are great places to do practice runs for a conference talk.
</p>
<p>
Reach out to the local groups in your area and let them know you have a
conference talk that you'd like to perform in their meetup as well. My
personal experience is that I discover a thing or two when I do the first full
run of my talk so every run not only improves my presentation skills and
lowers the stress but also makes the content better.
</p>
<p>
The best advice I've ever gotten for becoming better at doing talks and
presentations is this: "Record your talks – not to spot the mistakes but to
make sure you capture the things that went really well so you have a recording
and can write them down for future runs."
</p>
<p>
At my job, I'm lucky that we have
<a href="https://futurice.com/techweeklies">Tech Weeklies</a> – a weekly
internal meetup for developers to share what they know and have learned. I've
done a few practice runs in those over the years and it's been invaluable. If
you want to set up something similar in your company or team, I've written a
bit about how it works in
<a href="https://futurice.com/blog/tech-weeklies-as-a-learning-platform">a blog post</a>.
</p>
<h2 id="tip-4-read-a-book">Tip 4: Read a book</h2>
<p>
This is more of a general tip that I want to share.
<a href="https://www.goodreads.com/book/show/4865.How_to_Win_Friends_and_Influence_People">Dale Carnegie's How to Win Friends and Influence People</a>
has a rather off-putting name but for me, it's been one of the key books I've
read that taught me how to be a bit more social and find ways to do small talk
even when it doesn't come to me naturally.
</p>
<p>
It's also a rather short book and has very practical tips and examples so I
aim to read it at least once a year. Out of all books I've ever read, this one
has had by far the biggest impact in my professional and personal life.
</p>
<p>
Another book of the similar content is
<a href="https://www.goodreads.com/book/show/84699.Never_Eat_Alone">Keith Ferrazzi's Never Eat Alone</a>
that was equally life-changing for me when I read it late 2014.
</p>
<h2 id="tip-5-take-care-of-yourself">Tip 5: Take care of yourself</h2>
<p>
For me, the best part of any conference and main reason for me to participate
in those is to meet people. But it can sometimes get overwhelming. Here's the
big reveal: it's okay to take some personal time and space. Go for a walk,
take a nap or just sit in the corner and watch talks.
</p>
<p>
For Django Day Copenhagen, I had big plans for the post-conference day on
Saturday but when I woke up, I remembered that being active socially and
literally talking with people from early morning to midnight takes a toll. So
I let people know I'm taking a self-care day and only went out later in the
evening for dinner after feeling a bit better (and after evacuating the hotel
for fire alarm).
</p>
<p>
I think there's a lot of unspoken, imagined conference etiquette that doesn't
really exist. You don't literally need to sit through every talk and then feel
exhausted and fly home. For a smaller event, I tend to watch most talks but
especially for a larger multi-day conference, I pick the ones that are most
interesting and spend rest of the time either hanging out with people in the
hallways, going for a walk or taking some me-time.
</p>
Documentation: man pages vs tldr
2022-04-20T00:00:00Z
https://hamatti.org/posts/documentation-man-pages-vs-tldr/
<p>In today's blog post, I'm gonna compare two approaches to writing documentation: <em>reference</em> vs. <em>recipes.</em> Now, the point is not to determine which one is better – they both have their place – but to showcase the two approaches using two command-line documentation tools as examples.</p><p>Personally, I'm a fan of the <em>recipe</em> style. When I started programming back in the day, I ended up starting with PHP and probably the main reason why I ever learned to code was the User Contributed Notes section in PHP's documentation. Reading reference documentation like API docs or the reference part of PHP's docs is really hard. The more experienced you become in development, the easier they become to read and once you learn to read them, they become often the fastest because it's easier to find exactly what you need.</p><p>One downfall of recipes is that they are <em>incomplete</em>. Recipes only cover specific use cases and don't go further into describing the options and alternatives. So unless your use case is listed as a recipe, you'd be out of luck. So having a full and complete reference documentation is also needed.</p><p>Let's take a look at <a href="https://www.php.net/manual/en/function.rsort">the documentation page for PHP's rsort</a>. First, you'll see the function description, then a list of parameters, what the function returns and a few examples. That's the reference part (well, the examples are more of a recipe part already). And it's great when you want to see what order the arguments are passed or what flags does it support for sorting.</p><p>The reference documentation starts from the implementation and usage point of view: how is this feature made and how to use it.</p><p>But then there's the User Contributed Notes section, the recipes, provided by the community. Recipes start from a different place: they answer the question "how to do X?" with practical examples. For example, at the time of writing, the top note provides the code for "A cleaner (I think) way to sort a list of files into reversed order based on their modification date." It adds a bit of extra code around the function call itself because we rarely use these functions in isolation but as part of other code.</p><p>When I was learning computer science at the university, I bought a lot of programming cookbooks like <a href="https://rc2e.com/">JD Long's & Paul Teetor's R Cookbook</a> and <a href="https://www.oreilly.com/library/view/sql-cookbook/0596009763/">Anthony Molinaro's SQL Cookbook</a>. And I loved reading them more than I liked reading R documentation or SQL reference guides. Because they helped me learn how to solve real-life problems.</p><p>So let's take a look at two example cases from the command-line world: <a href="https://en.wikipedia.org/wiki/Man_page">man pages</a> and <a href="https://tldr.sh/">tldr pages</a>.</p><h2 id="man-pages">man pages</h2><p>First, let's take a look at man pages (short for <em>manual pages)</em>. They are used in Unix based systems as documentation for command line tools. If you're using Linux or macos, you can try them by typing <code>man tar</code> to see the documentation for <code>tar</code> command.</p><p>Man pages are a good example of reference style documentation.</p><p>For <code>tar</code>, the man pages starts like this:</p><pre><code class="language-bash">NAME
tar -- manipulate tape archives
SYNOPSIS
tar [bundled-flags <args>] [<file> | <pattern> ...]
tar {-c} [options] [files | directories]
tar {-r | -u} -f archive-file [options] [files | directories]
tar {-t | -x} [options] [patterns]
DESCRIPTION
tar creates and manipulates streaming archive files. This implementation can extract from tar, pax, cpio, zip, jar, ar, xar, rpm, 7-zip, and ISO 9660 cdrom images and can create tar, pax, cpio, ar, zip, 7-zip, and shar archives.</code></pre><p>Later, it'll list all the command line options and how to use them.</p><p>Man pages are very thorough but I find them often really difficult to read. When I was learning how to use unix-style systems and the commands, it wasn't uncommon to get the "just read the man pages" reply when asking for help. Honestly, that wasn't very helpful.</p><p>But as I've become more seasoned user of these tools, I often find myself opening up the man pages when I need to find some specific information about a flag.</p><h2 id="tldr-pages">tldr pages</h2><p>On the recipe side, we have multiple different tools but the one I prefer to use these days is <a href="https://tldr.sh/">tldr pages</a>. Their website says they <em>"are a community effort to simplify the beloved man pages with practical examples."</em></p><p>Hence, they are the recipe cookbook for using the same tools that man pages provides reference documentation for.</p><p>For <code>tar</code>, the tldr pages starts like this:</p><pre><code class="language-bash">tar
Archiving utility.
Often combined with a compression method, such as gzip or bzip2.
More information: <https://www.gnu.org/software/tar>.
- [c]reate an archive and write it to a [f]ile:
tar cf target.tar file1 file2 file3
- [c]reate a g[z]ipped archive and write it to a [f]ile:
tar czf target.tar.gz file1 file2 file3
- [c]reate a g[z]ipped archive from a directory using relative paths:
tar czf target.tar.gz --directory=path/to/directory .
- E[x]tract a (compressed) archive [f]ile into the current directory [v]erbosely:
tar xvf source.tar[.gz|.bz2|.xz]</code></pre><p>Where man pages start from the completeness of reference, tldr pages start from practical needs. You don't need to know all the dozens of flags available, if you just need to create or extract an archive. I'd argue it's definitely more beginner-friendly too.</p><p>Recipe documentation is often community-driven like is in the case of aforementioned PHP documentation and in the case of tldr pages. And that's a great thing because then the examples come from real life use cases rather than the developer themselves trying to think of possible cases. It does however need a community large enough to be useful which can be a challenge for newer tools.</p><h2 id="which-one-should-you-use">Which one should you use?</h2><p>As <strong>an end user, </strong>you should use both. Learning how to read reference documentation of your tools, libraries and APIs is a skill that will always be useful and help you get the answers straight from the source material. But for many day-to-day operations, recipes are wonderful. Despite using <code>tar</code> for a few decades already, I still always use <code>tldr tar</code> when I need to create something because the interface isn't very intuitive to me – <a href="https://xkcd.com/1168/">see xkcd 1168 for reference</a>.</p><p>As <strong>a technical writer, </strong>I think the ultimate goal is to provide both. But documentation efforts often boil down to resource management and making a lot of compromises. Making it easy for the user community to submit recipes in a knowledge base is very valuable. Or, for example if you run a more chat-based community, actively collecting good recipes from discussions and sharing them via your documentation is something worth its weight in gold.</p><p>Last fall I ran into <a href="https://twitter.com/Contextify1/status/1329833948147458049">a great Twitter thread about documentation and recipes by Alec Barrett-Wilsdon</a>. He wrote a lengthy thread about AWS documentation and his insights into how he would improve it to match the user needs better.</p>
My trip to Django Day Copenhagen 2022 and Stockholm
2022-04-14T00:00:00Z
https://hamatti.org/posts/my-trip-to-django-day-copenhagen-2022-and-stockholm/
<p>Django Day Copenhagen on April 8th 2022 was my first trip after the beginning of the pandemic and a return to a conference where I spoke remotely in 2020. Before the trip, I was slightly worried because <a href="https://hamatti.org/posts/last-two-years-have-been-awful/">the past two years of burnout and anxiety</a> meant it could either be a really good trip that helps me recover or added stress to already large mountain of personal problems. Spoilers: it turned out to be the first.</p><h2 id="the-trip">The trip</h2><p>As a non-flyer, my trips to conferences and meetups abroad are always an adventure. This time though, it went smoother than ever before. The ferry from Helsinki to Stockholm was super smooth cruising (which was lucky because I get seasick very easily) and the train from Stockholm to Copenhagen actually went all the way to Copenhagen which was a nice surprise after so many times it not doing that.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/speaker-dinner.jpeg" class="kg-image" alt="Speaker dinner with five people around a round table" /></figure><p>The evening before the conference, we met with some fellow speakers <a href="https://twitter.com/MHLut">Marijke</a>, <a href="https://twitter.com/pauloxnet">Paolo</a>, <a href="https://twitter.com/jvzammit">Joseph</a> and <a href="https://twitter.com/michjnich">Michael</a> for a lovely dinner. A dinner on the evening before the conference is one of my favorite things to do and it's a great way to connect with fellow speakers and participants. In previous conferences we had <a href="https://twitter.com/Hamatti/status/1179442613088395264">a great dinner in PyCon Estonia</a> and <a href="https://twitter.com/Hamatti/status/1189629212627521536">also in PyCon Sweden</a>. Also PyCon CZ had a great one but I was so nervous with my first international conference that I didn't take any pictures.</p><p>As a good example of how small the developer world is, it turned out that Michael had seen me speak in PyCon Sweden in 2019!</p><p>We also learned that most kitchens close at 21 in Copenhagen which caught us by surprise and we ended up roaming around the town for a long time before we found Hereford Beefstouw and got served really good steaks.</p><h2 id="the-conference-day">The conference day</h2><p>Already in 2020, even though my participation being remote, I got a really good sense of a lovely community and knew immediately back then that I wanted to make a trip to Copenhagen as soon as possible. And I wasn't disappointed with what I got.</p><p>The conference had 9 talks + 5 lightning talks and I feel like there was a really nice balance and combination of different type of talks.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/paolo-talk.jpeg" class="kg-image" alt="Paolo on stage speaking with a slide about local communities" /></figure><p><a href="https://twitter.com/pauloxnet">Paolo</a> opened the event with <a href="https://2022.djangoday.dk/talks/paolo/">a nice talk about his journey with Python and Django and the communities</a>. There were so many parallels to my journey in developer communities and I just ended up nodding along throughout most of the talk thinking to myself: "Yup, that's a great point".</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/mike-talk.jpeg" class="kg-image" alt="Mike speaking on stage with a slide about 12-factor app" /></figure><p><a href="https://twitter.com/michjnich">Mike</a> followed up with <a href="https://2022.djangoday.dk/talks/michael/">a talk about 12-factor app and how following those guidelines helped their company migrate 10k databases to a newer version with minimal issues and downtime</a>. Real-life cases are always fascinating and it was great to hear how to apply the principles into a project and see results.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/will-talk.jpeg" class="kg-image" alt="Wilhelm speaking on stage with a slide about how much more we read code than we write it" /></figure><p>The third talk of the day was by <a href="https://twitter.com/wilhelmklopp">Wilhelm</a> from <a href="https://kolo.app/">Kolo</a>. I felt like his talk was a really nice complementary view into similar things as what I talked about but with a different perspective and different solutions. He <a href="https://2022.djangoday.dk/talks/wilhelm/">talked about Kolo, a tool that integrates into VS Code and helps understand (and debug) HTTP requests in Django apps.</a></p><p>Fourth we learned about <a href="https://2022.djangoday.dk/talks/romulo/">WireMock and how to do end-to-end tests with Django using WireMock's mocking abilities</a>. <a href="https://twitter.com/romulojales">Rômulo</a> did a great job showcasing how to do it with a Minecraft server management app.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/joe-talk.jpeg" class="kg-image" alt="Joseph on stage speaking with a slide with a few code examples" /></figure><p>Django's ORM is very powerful and good tool but <a href="https://twitter.com/jvzammit">Joseph</a> <a href="https://2022.djangoday.dk/talks/joseph/">walked us through how inefficient SQL queries you can sometimes create if you're not paying attention</a> and how important it is to not only understand SQL as Django developers but also to keep it in mind when developing solutions with the ORM. He showed how to profile and analyze the calls and what different ways there are in the ORM to create more efficient queries without having to resort to RawSQL.</p><p>As my slot was closing in and I got more nervous, I was unfortunately only able to follow <a href="https://twitter.com/MariuszFelisiak">Mariusz's</a> <a href="https://2022.djangoday.dk/talks/mariusz/">migration talk</a> with one eye and ear.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/juhis-talk.jpeg" class="kg-image" alt="Me speaking on stage with a slide showing a photo I took two years ago when speaking remotely with two plushies behind my screen as my audience" /></figure><p>My talk was about <a href="https://2022.djangoday.dk/talks/juhis/">debugging Django</a>. As I've helped a lot of developers from first-timers and juniors to seniors, I've noticed a lot of inefficient patterns and even a lot of guesswork in most people's approaches to debugging. I talked about general mindset things as well as some tools that can help you become efficient debugging your Django application.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/friday-hug.jpeg" class="kg-image" alt="A friday hug group photo taken from stage" /></figure><p>And I got to introduce this community to a favorite of mine: Friday Hugs. I got a nice addition to my collection to remember this conference forever. You can see<a href="https://twitter.com/search?q=fridayhug%20from%3A%40Hamatti&src=typed_query"> my collection of Friday Hugs in Twitter</a>.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/cph-cake.jpeg" class="kg-image" alt="A can of coke and a piece of chocolate cake, taken outside on a sunny afternoon day" /></figure><p>And I finally got the Django Day Copenhagen cake I had been waiting for since 2020. In a beautiful spring weather no less.</p><p>Recovering my talk and the release of my nervousness, I completely missed <a href="https://twitter.com/johanneswilm">Johannes'</a> <a href="https://2022.djangoday.dk/talks/johannes/">talk about distributing Django projects via Ubuntu Snap</a>.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/marijke-talk.jpeg" class="kg-image" alt="Marijke speaking on stage with a slide about leaving your ego at the door" /></figure><p>The last talk of the day was <a href="https://twitter.com/MHLut">Marijke's</a> <a href="https://2022.djangoday.dk/talks/marijke/">wonderful talk about onboarding and setting up new developers for success.</a> She made so many great points about building psychological safety, about the culture of feedback and encouragement in teams, about documentation and many other things. This was another talk where I was just nodding throughout the talk and thinking: "Yup, that's it!".</p><p>Another favorite of mine in tech conferences are <a href="https://2022.djangoday.dk/talks/lightning-talks/">lightning talks</a>. I always aim to make a quick & fun talk that lets people laugh a bit at the end of a long day. In this conference, I talked about the wild wild world of different software version systems. It was based on <a href="https://hamatti.org/posts/how-would-you-compare-two-version-numbers/">a blog post of mine from last fall</a> and went through 22 slides in 4 minutes. And I managed to make most of the audience laugh for most of the 4 minutes so that was a success! </p><p>Other lightning talks saw <a href="https://twitter.com/romulojales">Rômulo</a> showcase how to unit test functions that use decorators, <a href="https://twitter.com/benjaoming">Benjamin</a> talk about Wagtail and its localization capabilities, <a href="https://twitter.com/JonG0uld">Jon</a> talk about job hunting and how to find what you're interested in and finally <a href="https://twitter.com/pauloxnet">Paolo</a> inviting us all to <a href="https://pycon.it/">PyCon Italia</a> this June. Awesome lightning talks all around!</p><h2 id="a-few-thanks">A few thanks</h2><p>First of all, I wanna thank <strong>the organizing team</strong> and especially Benjamin and Emil for organizing such a wonderful conference and building such a lovely and welcoming community. Everything was spot on and I had really good time throughout the event. As a speaker, I felt taken care of before and during the event in a way that made it super easy for me to focus on the most important thing: having a good time with people.</p><p>Thanks to all the fellow speakers, it was so great to get to know you before, during and after the conference ❤️. Meeting people who share a passion is by far the best part of developer communities and the reason why I spend most of my work and free time around them.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/best_bird.jpg" class="kg-image" alt="A group of people laughing around a table, looking into the camera" /></figure><p>To all the participants as well! I think I managed to chat with almost everyone during the event. And meeting everyone from Team <a href="https://en.wikipedia.org/wiki/Secretarybird">Best Bird</a> and finishing second in the Django Pub Quiz was a lovely finish to the conference day itself.</p><p>As I mentioned in the beginning, I didn't know beforehand if this would help me recover or throw me deeper into despair. I'm so happy it was the first one. I haven't been able to really relax and recover in two years but this trip definitely did it for me. I've slept better than in a long long time and I've been able to put away my stress and focus on having a good time, traveling and hanging out with the best people.</p><p>I dedicated the Saturday for relaxation and self-care which was abruptly interrupted as I woke up from an accidental nap when the fire alarm went off and I had to run down from 8th floor as the hotel was being evacuated. Luckily it seemed to be just a false alarm and the hotel didn't burn down. It's incredible what a rush of adrenaline can achieve.</p><h2 id="2023">2023?</h2><p>The next Django Day Copenhagen is planned for March of 2023 and if you are a Django developer with a possibility to travel to Copenhagen for the event, I can not recommend the event enough. It was a brilliant experience in a well-organized event with amazing people.</p><p>Or if you're not a Django developer, maybe I can interest you with <a href="https://react-finland.fi/">React Finland </a>that we're organizing in September 12-16th in Helsinki or <a href="https://2022.euruko.org/">Euruko 2022</a>, October 13-14th also in Helsinki? Both of those conferences have ticket sales open right now and I'd like to see you there.</p><h2 id="four-year-anniversary-at-futurice">Four year anniversary at Futurice</h2><p>Coincidentally my fourth work anniversary at <a href="https://futurice.com/">Futurice</a> also landed on this weekend and visiting Copenhagen, there was only one way to celebrate: immortalizing myself as a LEGO.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/futu-juhis-lego.jpg" class="kg-image" alt="A custom LEGO figure that looks like me in Futurice hoodie and jeans – my regular outfit" /></figure><p>There has been a lot of ups and downs in the relationship between me and the company over the years but this weekend was a really good reminder of why I love what I do for a living and how privileged and lucky I've been to get here.</p><h2 id="and-a-bit-of-stockholm-too-">And a bit of Stockholm too!</h2><p>On my way home, I stopped by in Stockholm for a few days to visit our new Stockholm office, to catch up with old friends and to meet so many colleagues face-to-face for the first time after a few years of becoming friends over video calls.</p><p>And yes, more eating! </p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/mrcake-cake.jpg" class="kg-image" alt="A huge slice of delicious caramel cake" /></figure><p>In 2019, I was visiting PyCon Sweden and a friend introduced me to Mr. Cake cafe. So when I returned to Stockholm for the first time since, I just knew I had to visit that again. Last time, the huuuuge American pancakes with ice cream defeated me and this year catching up with an old friend, the caramel cake had the same effect.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/04/stockholm-futu-dinner-2.jpg" class="kg-image" alt="Six people eating fancy dinner in a restaurant" /></figure><p>I'm so lucky I get to work (and enjoy life) with such amazing people. Thanks to all the Stockholm colleagues who made my visit such a great one. </p><h2 id="the-benefits-of-remote-work">The benefits of remote work</h2><p>Whenever the discussion of remote work comes up, these kind of trips are the big reason why I'm an advocate for it in my life. For me it's not about working from home: it's about being able to work from anywhere so I can spend more time traveling or spending time with friends as the work can be adjusted to the situation.</p><p>In addition to the work stuff I did at the conference and at our Stockholm office, I was able to do full work days remotely from trains, hotels and offices thanks to the power of the Internet. I can have meetings to Finland and Germany, do internal comms in Slack, do external comms with partners and especially during the low-internet moments in trains and ferries, I got a lot of documentation written – although I learned to my detriment that editing a Confluence page requires constant internet connection which was a bit of an inconvenience.</p><h2 id="few-bits-of-interesting-stuff">Few bits of interesting stuff</h2><ol><li>The light switches in Denmark work exactly the opposite from how they do in Finland and it is confusing as heck.</li><li>Many hotels have art in their rooms to keep guests cultivated and inspired (I guess that's why they do it?). My hotel room had a picture of their meeting room because the last thing I want to be reminded of before going to bed is that meetings exist. Happy dreams!</li><li>My hotel room also didn't have a ceiling lamp. Like at all. It just had a variety of small led lamps scattered around the room with individual light switches and it was always rather dark.</li><li>Restaurants close their kitchens in Copenhagen at 21 which turned out to be very early and causing a bit of a dilemma for a hungry group of developers on a Thursday night.</li><li>It felt very different to travel somewhere where the pandemic seems to be pretty much forgotten. I didn't see a single person wear a mask in Denmark and pretty much none in Sweden either.</li><li>I learned that in Sweden (or at least in Stockholm), dancing is regulated and you can't just start busting out moves wherever. That made me sad.</li><li>I had ZERO panic attacks during the trip despite many possible triggers. ❤️</li></ol>
Listen to everyone, listen to no one
2022-03-30T00:00:00Z
https://hamatti.org/posts/listen-to-everyone-listen-to-no-one/
<p>In general, I don't like to give advice because I realize that I can only give advice based on the limited experience and understanding that I have. However, over the years I've ended up doing a lot of coaching and mentoring in different setups: coaching entrepreneurs, sharing my tips about work life and career stuff to students and helping new people get into technology. And sometimes I facilitate those coaching sessions helping coaches and students find each other.</p><p>Over the years, my main guideline that I try to always bring up before is <strong>Listen to everyone, listen to no one.</strong> It's catchy and rhymes. Both sides of it are hyperboles. But what does it actually mean?</p><h2 id="seek-advice-opinions-and-experiences-wide-and-far">Seek advice, opinions and experiences wide and far</h2><p>The first part, <strong>Listen to everyone</strong>, is my advice for seeking advice from different people. When I worked at <a href="https://boostturku.com/">Boost Turku</a> running our Startup Journey accelerator program for early stage startups, we had 30-40 coaches during 10 weeks helping the entrepreneurs to focus their ideas, hone their plans and polish their presentation. With so many coaches, most of them entrepreneurs themselves, it was inevitable that there would be a lot of different and even opposing thoughts, ideas and suggestions.</p><p>By exposing your ideas and thoughts to different people who've had different experiences and have different opinions on things, you'll get a lot of signals, some stronger than the others. And more opinions there are on a topic, more likely you are to hear something you had never thought off. Some of those may be good ideas to ponder further and some are just new because the person giving the advice might not have enough experience on the particular topic or field.</p><p>If you're a software development student and you're thinking about which technology to pick, you'll get almost as many different opinions as you'll have people you ask from. And quite often technologists tend to be quite strong with their opinions and obviously can best argument for the technology they are most familiar with. So seek out varying opinions and aim to make the decisions not based on which one gets most "votes" but based on the arguments given for those opinions.</p><h2 id="don-t-take-every-advice-at-face-value-only">Don't take every advice at face value only</h2><p>While the first part is crucial in finding different advice based on different experiences and knowledge, it can become quite challenging to navigate without the second part. <strong>Listen to no one</strong> is equal amounts hyperbole as the first one. It doesn't mean literally <strong>no one</strong> but rather <strong>don't believe any one advice at face value only.</strong> No matter what you seek, if your web is cast wide enough, you'll inevitably get contradicting advice and clearly you cannot follow two contradicting advice at the same time.</p><p>Important thing is also to realize and remember that most often peoplewho are asked to give their advice, are not even trying to say that their advice is the only true one that everyone should follow. It's just not usually said out loud. </p><p>I have a talk that I've done quite a few times over the past 2 years where I talk to students about how one can get the best out of their side projects when applying for jobs in the software industry. In that talk, I talk about how to come up with ideas for these projects, how to document and present them and what to talk about them when applying for jobs and when in interviews. It's based on my own experience as a hobby developer becoming an employed software developer and things I've learned about it on the way.</p><p>But it's not the only way and I'm by no means implying that if you don't spend all your free time coding, you can't be successful. It's just the advice that I'm best at giving because it's what I know best through personal experience (and interests). I think it's a good piece of advice – for those who want to follow it – but it shouldn't be heard as the only truth. So it's important to listen to various different advice and then find the way that best fits you and your situation.</p><p>Unless you're getting advice directly from someone who's gonna assess your decisions and work (like a recruiter from a company you're applying to), be also careful on whose advice you listen the most. Try to avoid falling into the trap of putting too much value on someone's opinion based on their status or a position they've reached.</p><p><a href="https://en.wikipedia.org/wiki/Survivorship_bias">The survivorship bias</a> is strong and it's often hard to recognize. When asked someone why they think they became successful or achieved something, they tend to answer what they believe to be the case even if there's no real evidence that waking up at 5am or only wearing blue sneakers on every other Thursday actually had anything to do with their success. Even if someone would do exactly the same things I've done, it's very likely they would end up in a very different situation in life because there are too many variables to know.</p><p>In the end, it'll always be you who makes the decision what to do. And it means that despite all the advice and advice-givers, in the end the responsibility to deal with the outcome is with you. Almost always the best way is to ignore most of the advice.</p>
Last two years have been awful
2022-03-23T00:00:00Z
https://hamatti.org/posts/last-two-years-have-been-awful/
<p><em>Content warning: burnout, depression, stress. In this blog post, I'll discuss burnout, depression and related topics from the perspective of how it has affected me over the past two years. If you want to avoid those topics for your personal self-care, please skip this post and check out my earlier posts at <a href="https://hamatti.org/blog">/blog</a>.</em></p><h2 id="why-did-i-decide-to-write-and-share-this">Why did I decide to write and share this?</h2><p>I will admit, it's not the easiest thing to do. I keep thinking of a potential future employer who thinks I'm unemploayble because of my struggles. And that's not an easy thing to shrug off from my mind – as you'll learn by reading this, the future has been biggest stress factor for me during this time.</p><p>But I figured it could be helpful. I keep talking with developers all around the world and there aren't a lot of discussions where it doesn't end up into some variance of "Yeah, I'm actually also really tired". After I've shared my situation, I've noticed people have been more open to share theirs. And even though I'm not a medical expert or psychologist in any way, even I can see the distress signals from some people before they notice it themselves. Because I've been there.</p><p>If you're a team lead, a supervisor or an employer, please help your team get through this. They may not tell you they are tired or close to a burnout if you ask – it's a scary thing to admit when it's your job on the line. So if you can, please be proactive and reduce the work load for a while, encourage your team to take some extra paid time off and be a good human. I'm kinda really worried about all those people. I don't want them to go through what I've been going through.</p><h2 id="my-story-from-the-past-two-years">My story from the past two years</h2><p>It's now been over 2 years since everything was shut down for the pandemic. I've tried to write my feelings and thoughts down so many times but sharing something so personal and finding the right way to say it hasn't been easy. Most recently I've talked with a lot of people who've been telling me how they've felt the same during their burnout and that things have gotten better, no matter how impossible it has felt during the time.</p><p>This has been inspiring to me and I figured maybe me sharing my thoughts could also be helpful and to others. Even just to hear that we're not the only ones who are struggling because we don't often see these things discussed. We all put on some sort of a mask infront of ourselves and force a smile on our faces when talking with people.</p><p>And there's also the really weird feeling of guilt: in the end, <em>everything's quite okay, I can't be feeling this bad/tired/depressed.</em> I haven't lost my job, my close family has been staying healthy and it's easier than ever to order takeaway delivered to home and buy books, movies and video games digitally online. Not to mention the horrors of war that is ravaging in some parts of the world.</p><p>But I guess human mind doesn't work like that. There's no objective level of bad that needs to be reached until things get rough. Our minds seem to be flexible to both directions: to adjust to new things but also to be very subjective to its own experience. And I've been trying to tell myself that it's okay.</p><h2 id="a-little-bit-of-context-backstory">A little bit of context & backstory</h2><p>To understand a bit better how I ended up here, let's start by talking about what is it that I do. The core of my approach is to bring developers together to learn from each other, to inspire each other and to have a good time. Everything I do, I look through these lenses. In day-to-day, it often happens by organizing events like meetups and workshops but also via blogging, running newsletter, doing coding livestreams on Youtube and building online communities.</p><p>In 2019 and early 2020, it wasn't uncommon for me to have 5 evenings of events with different audiences and level of participation: sponsoring local communities' meetups, organizing our own meetup events, speaking in meetups/conferences or running workshops for either early career developers or early stage startups. Sprinkled in were <a href="https://hamatti.org/posts/helsinki-dev-lunch/">lunches</a> either 1-on-1 with devs or open lunches for local groups, meetings and afterworks and a lot of fun activities.</p><p>And that's also what I did for hobbies: I run different developer activities like <a href="https://hamatti.org/turkufrontend/">Turku <3 Frontend</a>, <a href="https://hamatti.org/codebase/">codebase</a> and teaching programming & mentoring juniors.</p><p>When people have lately asked me "What do you do for a living?", I've told them "All things forbidden in the pandemic." In March 2020, most everything was taken away: work, hobbies and friendships.</p><h2 id="2-years-of-isolation">2 years of isolation</h2><p>Going from meeting 500 people a month to seeing 4 people in a year is quite a mental shock. In addition, since my job is so dependent on other people wanting to share, to participate and be active, it wasn't just my inability to adjust to the new situation that made things difficult but it was also the necessary shift of other people's focus towards survival in pandemic that made things difficult. Even when I had a great idea, I still had to overcome the challenge of getting other people involved.</p><p>When the pandemic and the restrictions first started, I sat on a park bench having just cancelled 20+ events I had planned and booked for the next few months. I was optimistic about things: <em>"I'm more than a one-trick pony, surely I'll figure out a new way to do these things."</em></p><p>Turns out, it wasn't that easy. Especially when combined with a 24/7 stress, lack of sleep (it's been 2 years and I think I've had a good sleep on 4 nights during that), feelings of inadequacy and my own survival mode. I crashed and burned really hard. I've been on a burnout related sick leave twice during these two years but I don't think I've been well even outside those times: I've just tried my hardest to push through.</p><p>I've tried to reinvent myself so many times. I started <a href="https://hamatti.org/codebase">codebase</a> livestream show because I wanted to provide a platform for interesting people to share their passion for their technology with people in my communities. I've focused on a lot of written content (like in this blog), spoken in a lot of tech meetups/conferences remotely from my bedroom and even hosted a remote christmas party to one of my communities.</p><p>Remote events are a nice idea in a difficult situation but it's so exhausting. I once did a 3+ hour lecture at the university via Zoom and I was so done after that since it was pretty much just a monologue staring at a webcam. As a aspiring speaker I wanted to keep up with my speaking gigs but it's gotten harder and harder every month.</p><p>As someone who lives alone, it's been especially rough to be all alone with my thoughts all day long. I've played 141 video games, watched hundreds and hundreds of movies and tv show episodes and have averaged 70 hrs of Youtube per week throughout the pandemic.</p><h2 id="the-stress-eats-everything">The stress eats everything</h2><p>Maybe the worst thing and at the same time the most difficult thing to explain to others is how devastatingly horrifying continued stress is. Trying to keep up with work, every ounce of energy I have goes towards that. It means that if I manage to wake up at 9.00, I might be completely out of energy by 9.05. </p><p>And the stress never ends. Since there are so often days when I don't manage to get a full day of work done, the work day kinda never ends and the stress about undone stuff keeps piling up.</p><p>All that lack of energy leads quickly to other problems too. I haven't exactly been eating healthy or exercising because I simply lack any energy, both physical strength and mental energy, to force myself to do those things. I've never been in this bad shape before in my life. And while I know that part of the solution would be to eat healthier and start exercising, it's so hard when there's zero energy. And it's so hard to explain to someone how one can be so out of energy for so long. I might cook once a week, or a month or like three months. When I have a really really good day.</p><p>There has been plenty of Fridays when on Friday evening, I'm already exhausted for the next week. And I know the weekend isn't replenishing any of that energy because the stress never goes out. It doesn't matter if it's a work day, a weekend, a holiday or a sick leave day. Most of the problems keep accumulating on all of those days.</p><p>I've reached the dangerous and scary point where I've started to lose interest towards stuff that I used to love. People who've been here before me (and probably the past me could also tell it if it wasn't drowned in all this mess) and bounced back keep telling me that no matter how impossible the situation seemed at the worst time, once they got healthier and bounced back, they regained their passion towards all those things.</p><p>That's a thought I cling onto with everything I have. And I'm trying my earnest to catch those opportunities with the hope that the spark will happen and light my passion again. But it's hard. Because it feels like I'm not in charge of this ride.</p><h2 id="pulling-the-rug-out-from-under-my-feet">Pulling the rug out from under my feet</h2><p>Last fall we had a small break of good stuff. In October, restrictions were lifted for a moment and I jumped right in. In October and November I managed to do 33 events ranging from local meetups to speaking in DevRelCon. I also had quite a few booked for December and January when at the beginning of the month restrictions came back and once again, I had to cancel it all.</p><p>I knew before hand that if it would happen, I couldn't handle it. And I couldn't. I canceled all the events and called my doctor. I ended up being on a sick leave for the next three months. Because of all the stress and the anxiety of "what if I'll never bounce back", the sick leave was necessary but it wasn't a stress-free experience. The stress about future was still there every waking hour.</p><p>In March, the restrictions were lifted and I'm back at workbut return hasn't been easy. Just last week on Wednesday, enough of stress factors piled up at the same time and I had a mental breakdown. I ended up crying in a meeting room for few hours and had to do some last minute shuffles to get that evening's event done without having to be on the stage as I had originally planned. And I ended up cancelling all the work for the rest of the week.</p><p>Now I try to go one day at the time to see how long I manage to get. I have a few potentially positive things in the horizon: a few workshops, a trip to Django Day Copenhagen and a talk there, a trip to hang out with my colleagues in our Stockholm office and a few dozen events. I'm just really scared since I don't know what to do if those don't spark the passion and get me back on my feet.</p><h2 id="it-s-hard-to-see-those-small-victories">It's hard to see those small victories</h2><p>Even though it feels like I haven't done anything in these past two years other than sitting on the couch and playing video games and stressing about it, I know that when I start listing out the things I've managed to do, the list is not insignificant. I'm also extremely lucky to have had the support that I have from my employer and my colleagues.</p><p>However, this is not a problem that is solved by rationale and logic. It's a monster inside my head that keeps all the good things out. It's my own mind telling me I suck and that I'll never succeed again. It keeps telling me I was never good at anything. And even though I know that's not true, it's really hard when it's your own mind telling you that stuff.</p><p>And it's so incredibly difficult to explain to anyone who hasn't gone through it themselves.</p>
I'm speaking in Django Day Copenhagen 2022 in April
2022-03-16T00:00:00Z
https://hamatti.org/posts/im-speaking-in-django-day-copenhagen-2022-in-april/
<p>
I'm so excited to return to in-person tech conferences after such a long time.
We were able to run a few meetups in the short period of time last October
& November when pandemic situation seemed to ease out for a moment.
However, it's been since November 2019 and PyCon Sweden that I've been on a
bigger conference in person.
</p>
<p>
I'm especially excited to get back to the stage with Django Day Copenhagen. I
spoke in the event remotely in 2020 (<a href="https://2020.djangoday.dk/talks/contemporary-documentation/">see my talk about Contemporary Documentation here</a>) and really enjoyed the small interactions I had with the community and have
been looking forward to an opportunity to take the train south and visit
Copenhagen.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2022/03/django-day-cph-debugging-django.png" class="kg-image" alt="Debugging Django, talk by Juha-Matti Santala. No matter if you’re a junior developer just starting your career or a seasoned senior, you’ll run into moments when the software isn’t doing what you’re telling it to do. How you approach the situation when it happens will have a huge impact on your productivity, so in this talk I’ll walk you through both technical tools and solutions as well as some valuable non-technical approaches." />
</figure>
<p>
On April 8th, 2022 the event is organized again in-person and I'll be there
speaking on the topic of
<a href="https://2022.djangoday.dk/talks/juhis/">Debugging Django</a>.
</p>
<p>
Debugging is one of my favorite parts of software development even though it's
most often done under a bit of stress and anxiety since it happens when things
go wrong. I've been helping out a lot of people from juniors in programming
workshops to seniors at work to debug and to put into good use a set of
processes and tools to make debugging more effective and enjoyable.
</p>
<p>
I'll be sharing my tips on debugging in general and some tools for debugging
specifically Django in Django Day Copenhagen on April 8th, 2022. Check out the
<a href="https://2022.djangoday.dk/">event website</a> for tickets if you're
interested in joining this great event or if you're a developer in Copenhagen
who'd like to meet and grab a drink or eat cake and play board games during
that weekend, let me know via
<a href="https://twitter.com/hamatti">Twitter DMs</a>, email
(juhamattisantala, gmail, com) or in the Django Danmark Zulip platform if
you're part of that community. I'm always eager to meet new people, to discuss
technology and to have a good time.
</p>
Added custom highlight function for nhl-235
2022-03-09T00:00:00Z
https://hamatti.org/posts/added-custom-highlight-function-for-nhl-235/
<p>When I <a href="https://hamatti.org/posts/introducing-235/">originally started developing</a> my <a href="https://github.com/Hamatti/nhl-235">nhl-235 project</a> in the beginning of 2021, it was heavily inspired by the teletext service Finnish Broadcasting Company YLE had been running for decades – and promptly named after it as well.</p><p>One key feature that I didn't implement (even though I wanted to), was to mimic the way the original highlights Finnish players' points so it's always easy to see on a glance how my favorite players are doing. The main reason for not being able to implement it, was that there was no nationality information in the scores data the API provided and I didn't want to manually maintain a list of players that would always be out of date as players come and go.</p><p>Finally last week I got an idea that in retrospective seems so obvious I can't understand why it never occurred to me before, during this past 1+ years that I've developed and used the application. So last weekend I released version 1.2.0 that allows the user to create a custom list of players who they want to highlight and by using a <code>--highlight</code> flag with the tool, to enable this feature.</p><h2 id="custom-highlights">Custom highlights</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/03/nhl-235-without-highlight.png" class="kg-image" alt="Game score between Buffalo and Minnesota showing 9 goals all in cyan" /><figcaption>Here's the output with running <code>235</code> without the highlight option</figcaption></figure><p>When you run <code>235</code> without any options, the default colors are cyan for regular goals and magenta for shootout winner.</p><p>To enable the highlight, first you need to create a configuration file into your home folder called <code>.235.config</code> and add there one player (last name only) per line the players you want to highlight, as shown below.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2022/03/235-config-file-2.png" class="kg-image" alt="Terminal output of file .235.config with five last names listed" /><figcaption>An example config file</figcaption></figure><p>It's a very basic version currently as it only works on the basis of last names and for some players who share their last name with others, you'll get false positives. For me, this was a trade-off I was willing to have since I still know where my favorite players play so it doesn't really bother me if I'll see some extras highlighted.</p><p>To highlight the results, you need to run <code>235 --highlight</code> and you'll get your favorite scorers highlighted in yellow:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/03/nhl-235-highlighted.png" class="kg-image" alt="Scores of the Buffalo Minnesota game with three goals highlighted in yellow" /></figure><h2 id="looking-under-the-hood">Looking under the hood</h2><p>Since this project is my learning project in coding Rust, I want to share a few thoughts about developing this feature as well.</p><p>First of all, my last release before this was in November so it's been over 4 months since I've written any Rust code so I felt really rusty (no pun intended) to get started again and it felt like everything was so difficult to remember how to do.</p><p>I decided to use <a href="https://crates.io/crates/dirs"><code>dirs</code> crate</a> to make my home folder pathing OS agnostic.</p><pre><code class="language-rust">// StdError here is std::io::Error
fn read_highlight_config() -> Result<Vec<String>, StdError> {
// home_dir() is dirs::home_dir
let mut config_file = home_dir().unwrap();
config_file.push(".235.config");
let mut file = File::open(config_file.as_path())?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let highlights: Vec<String> = contents
.split('\n')
.map(str::to_string)
.filter(|s| s != "")
.collect();
Ok(highlights)
}</code></pre><p><a href="https://hamatti.org/posts/learning-rust-2-option-result/">As I've written before</a>, handling things in <code>Option</code>s and <code>Result</code>s has been a bit of a challenge and especially now after months of not writing Rust and <code>dirs::home_dir()</code> returning <code>Option<PathBuf></code>, I had more problems with figuring out when to unwrap and so on than I care to count.</p><p>Finally I was able to create the function above that reads the config file and returns a vector of the names. I then pass that vector from my main function down to the printing functions and added a new if/else branch to see if a scorer was in the highlights vector and then print it in yellow if it was:</p><pre><code class="language-rust">if home.special {
magenta_ln!("{}", message);
} else if highlights.contains(&home.scorer) {
yellow_ln!("{}", message);
} else {
cyan_ln!("{}", message);
}</code></pre><h2 id="future-considerations">Future considerations</h2><p>There are a couple of things I might look into in future:</p><ol><li>Using full names of players to narrow down false positives</li><li>Making the config file more config-y and allow configuring things like setting default flags for highlights and future features so users don't have to always use a flag or set a custom alias (which is my current workaround)</li><li>Using a config file management library like <a href="https://docs.rs/confy/latest/confy/">confy</a> if I end up implementing #2. For now though, reading a list of names manually is good enough.</li></ol><h2 id="install-the-newest-version-of-235-now">Install the newest version of 235 now</h2><p>You can install the newest version of 235 either from <a href="https://crates.io/crate/nhl-235">Rust's crates system</a> with <code>cargo install nhl-235</code> or by <a href="https://github.com/Hamatti/nhl-235/releases/tag/v1.2.0">downloading binaries directly from project's GitHub page</a>.</p><p>If you're using 235, I'd love to know so don't hesitate to either <a href="https://twitter.com/hamatti">tweet at me</a> or email me at juhamattisantala at gmail dot com.</p>
Rant: Please stop ruining the search
2022-01-19T00:00:00Z
https://hamatti.org/posts/rant-please-stop-ruining-the-search/
<p>Search is one of those features that have gained so much from the evolution of technology, from machine learning and the industry's skills in building efficient systems.</p><p>But over the past few years or so, I think we've crossed past the line of improvements and tipped over to start going backwards. And it has nothing do with skills or abilities of developers involved but rather the fact that product people have stopped caring about building products with a user-first mentality and replaced it with a company-first mentality.</p><p>And that really pisses me off.</p><h2 id="exhibit-a-netflix">Exhibit A: Netflix</h2><p>If you're not familiar with what I'm talking about, let's start by taking a look at Netflix. I've been a Netflix subscriber since it became available in Finland around 2011-2012.</p><p>Imagine for a moment it's Friday night. You want to relax by watching a movie by a great actor Nicolas Cage. You type it into the search box and – at least if you're like me – expect to get a list of movies starring Nic Cage.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/01/netflix-search-nicolas-cage.jpg" class="kg-image" alt="A Netflix search view with Nicolas Cage query showing 20 movies, not all featuring Nic Cage." /></figure><p>First, we get a few movies starring mr. Cage as we'd expect but quite quickly we run into movies like The Da Vinci Code and The Book of Eli – neither which star Cage. And due to the way the Netflix UI is built, there's no indication of when the search transitions from showing <strong>search results </strong>into showing <strong>recommendations.</strong></p><p>Netflix's search is not built to serve the user. Imagine having to check <a href="http://imdb.com/">IMDB</a> for every movie to see if the actor you searched is in it or not – after already having searched the streaming library for it.</p><p>And this is not due to the search engine accidentally thinking that the query <em>Nicolas Cage</em> somehow relates to these titles. It's purely showing you similar movies and series that it thinks you might enjoy.</p><h3 id="a-workaround">A workaround</h3><p>There is a workaround for this problem when searching for specific actors: you can find a movie they are featured in, go into that movie's detail page and click their name in the actor list. This opens up an actor's page only showing movies featuring them.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/01/netflix-nic-cage-page.jpg" class="kg-image" alt="Netflix view for Nicolas Cage's actor page" /></figure><p>So Netflix knows that they only have 7 movies starring Nicolas Cage but clearly are not confident enough that any of them would be interesting enough to keep me in the platform so in the search results view they try to make it look like they have way more content than they actually do.</p><h2 id="exhibit-b-youtube">Exhibit B: Youtube</h2><p>Google got big and successful by building a search engine that was way better than anything else in the market. It has then become a big player that builds anything and its products like Gmail and Drive are notorious for having really badly functioning search (especially for a company known for its search).</p><p>However, they don't seem to be maliciously bad like Youtube's search these days. Youtube does a similar thing as Netflix but not quite as bad.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/01/youtube-search-mountain-biking.jpg" class="kg-image" alt="Youtube search for "mountain biking" showing "Previously watched" videos like Pokemon TCG" /></figure><p>Here's a search I made for mountain biking. I scrolled a bit down and at one point, instead of showing me relevant search results, it started showing me videos I've already watched that has nothing to do with mountain biking. Berm Peak Express's avocado toast video is at least on a mountain biking video but AzulGG's Pokemon TCG content is not about mountain biking and I think Youtube's search algorithm knows it. It just wants me to continue watching it more than it wants me to watch what I searched for.</p><p>The reason it's not as bad as Netflix's search results is that at least it's telling me it's now showing something else. It also has similar sections for "People also watched" which is often more related but can still be something very different and "People also search for" which is often not very interesting and helpful.</p><p>It is still fundamentally ruining search as a functionality in favor of recommendation engines. Some growth hacker somewhere must be very proud looking into the data showing that their decision to ruin search is showing up as short-term increase in engagement on the platform.</p><h2 id="the-deep-problem-of-engagement-and-recommendations">The deep problem of engagement and recommendations</h2><p>Even though in this post I merely talked about search, the problem has existed for a long time and search is just one of the newest victims of it. For years, social media platforms filter what you see, overriding what you have signaled you want to see by following or subscribing to content. Twitter tries to extend your feed by showing what people you follow like or showing random (well-performing) tweets on a topic you've engaged with in the past. </p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/01/instagram-all-caught-up.jpg" class="kg-image" alt="Instagram feed showing "You're All Caught Up" banner, followed by "Suggest Posts"" /></figure><p>Some years ago, Instagram hid all the images you had already seen and show a "You're All Caught Up" banner. They initially said it was to encourage people to stop scrolling through Instagram, framing it as a mental health and well-being thing. It didn't take them long to start using it to their advantage again by showing "Suggested Posts" instead of the posts you as a user have decided you want to see. I'd rather see my friends' posts again than any of Instagram algorithm's recommendations.</p><p>These are all big reasons why I'm less and less interested in any social media platforms. I like to read my blog posts with an RSS reader because it doesn't hide the content from me or inject other content in the middle. That's also why I offer a <a href="https://hamatti.org/feed/feed.xml">RSS feed for my blog</a> and <a href="https://hamatti.org/feed/weekly-feed.xml">my Weeklies notes</a> so people who want to read my content can do that without the interception of social media algorithms.</p><p>Because let's be real: these algorithms are not for the benefit of the user or the human. They are used for brutally optimizing engagement which means that content that is clickbaity, provocative or simple gets way more exposure than other content.</p><p>In Youtube, this is heart-breaking for me when I see a channel announce they change their card game tournament format into shorter matches because longer content doesn't get rewarded by Youtube algorithm – even if that would be detrimental to the competition itself.</p><p>Recently a colleague shared some valid tips for how to post in LinkedIn and out of the 10 tips or so, 70% were about pleasing the algorithm and many of them actively hostile against the people we're supposed to write for (for example, putting links into comments). But if the option is that your posts and writings are not visible to people, most people choose the algorithm over the human.</p><h2 id="conclusion">Conclusion</h2><p>I don't know if there's anything to do to make a difference. Personally I'm kinda getting burnt out by everything related. I don't want to write worse content just to please an algorithm and I'm tired of everything being a popularity game where you succeed by sacrificing humans to algorithms.</p>
I started writing Weeklies
2022-01-12T00:00:00Z
https://hamatti.org/posts/i-started-writing-weeklies/
<p>A while back, I started a very personal newsletter called <strong>What's up with Juhis </strong>where I shared monthly what I've been doing: new blog posts, videos, talks and so on but also notes about my personal life. At the end of each monthly newsletter, I shared 2-3 things created by other people: articles, blog posts, videos, tweets, art and so on.</p><p>Recently I participated in a discussion about Javascript's callback functions and I remembered I had read this great blog post about it but I couldn't remember who wrote it. Luckily, I had shared it with my newsletter readers so I dove deep into the archives and found the link to share.</p><p>That inspired me to start collecting those links somewhere else in a public place. A place to share with others, kinda in a newsletter format but not through a full newsletter system.</p><p>Another thing that inspired me to start writing these notes were Marko Wallin's Rule of Tech blog's <a href="https://ruleoftech.com/2021/short-notes-on-tech-50-2021">Short Notes on Tech</a> and a Finnish daily tech news newsletter <a href="https://www.transistori.com/">Transistori</a>.</p><p>I ran the newsletter down a while back since it wasn't quite doing what I wanted it to do or be. I'll most likely revisit the idea but meanwhile, I'm doing this.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2022/01/weeklies-banner.png" class="kg-image" alt="Weeklies" /></figure><h2 id="introducing-weeklies">Introducing: Weeklies</h2><p>Weeklies, living on this website at <a href="https://hamatti.org/weeklies">/weeklies</a>, are short collections of notes mostly on things I've read or watched and wanted to share. I'm not set on stone with the format yet so there may also be other stuff as I discover what I want to share in the Weeklies format.</p><p>I'm expecting to write them once a week on Sundays (and still continuing to write these blog posts of my own every Wednesday) since I tend to read a lot and run into interesting articles all the time but I'm not hell-bent on keeping that schedule. If there's a week without anything note-worthy, then I'll skip it.</p><p>And just like my blog, the content will be mostly developer related but also all over the place – things that brought me joy.</p><p>I have already written the first two weeklies, <a href="https://hamatti.org/weeklies/2021-52/">one at the end of 2021</a> and <a href="https://hamatti.org/weeklies/2022-01/">one last Sunday</a>, go check them out!</p><p>And if you want to subscribe to those, I've created <a href="https://hamatti.org/feed/weekly-feed.xml">an RSS feed</a> for them as well so you can get my weekly notes to your favorite RSS reader fresh from the digital bakery.</p><h2 id="a-sneak-peek">A sneak peek</h2><p>Here's the 01/2022 Weeklies as a sneak peek for the blog readers:</p><p><a href="https://blog.stephsmith.io/how-to-be-great/"><strong>How to Be Great? Just Be Good, Repeatably</strong></a></p><p><a href="https://twitter.com/oehnstro">Oskar</a> shared this on his Twitter at the start of the year and while I don't agree some of the stuff (especially some of the quotes that reek hard survivorship bias), it's a great reminder of the importance of compounding returns and focusing on honing the process over an individual outcome.</p><p>I've been really struggling with this throughout my life but especially during the pandemic when it feels like I'm not making any positive progress and don't even have the ability right now to do anything about it. Being on a plateau of progress feels like such a defeat, even though it's inevitable.</p><p><a href="https://ethereamagazine.com/"><strong>Etherea Magazine - Australian Science Fiction and Fantasy</strong></a></p><p>Since last fall, I've been a subscriber to Etherea Magazine. I ran into it in Imgur and since I don't really have the focus these days to read full-length books, Etherea's short-story format has been incredible. It's perfect for reading while commute, drinking morning coffee or taking a short break at work. My favorite story so far is from the first issue (August 2020). It's written by Alexander Funk and it's called <em>Inheritance</em>. Highly recommend checking it out.</p><p><a href="https://www.channel4.com/programmes/jimmy-carrs-i-literally-just-told-you"><strong>I Literally Just Told You</strong></a></p><p>I'm a huge fan of British comedy and game shows and Jimmy Carr hosts the new I Literally Just Told You game show all about paying attention and remembering what's happening. So far, there's been 4 episodes and each on has been very uniquely different: first one being a rather serious quiz game, second one being so hilarious and fun and third being a celebrity episode with such an awesome panel. Fourth one was back to more serious but a really high quality quizzing with a great storyline. I wish they will direct it more towards the funny side in the future since that just works so much better.</p><p><a href="https://jacobian.org/2021/mar/9/coworking-to-write-more/"><strong>Coworking With a Friend to Write More</strong></a></p><p>Accountability buddies are a fantastic idea and something I'd like to explore more to bring more structure to my writing process and also for more than writing.</p><p>However, that has to wait for better times so I'm keeping this article as a reminder for myself to set something like this up when I'm less burnt out.</p><p>Currently my blog writing is often "I'll write something on the last night before Wednesday" and I'd like to bring more pre-planning and organization back into my process.</p><p><em>If you want more Weeklies, head over to <a href="https://hamatti.org/weeklies">/weeklies</a> and subscribe to the RSS feed.</em></p>
My least favorite board game genre: co-op games
2022-01-05T00:00:00Z
https://hamatti.org/posts/my-least-favorite-board-game-genre-co-op-games/
<p>Long-time readers already know that I'm very passionate about board games. Last fall, I wrote about a genre of games I – to my own surprise – found a lot of love for: <a href="https://hamatti.org/posts/roll-and-write-board-games-are-awesome/">the roll and write games</a>. During Christmas holidays, I played quite a few games (namely, Gloomhaven, TEAM3 Pink and Marvel United) from the genre I usually avoid: co-op games.</p><p>I like teamwork and when it comes to sports or video games, I absolutely love playing and winning together but unfortunately the same concepts don't transfer well to board gaming.</p><h2 id="playing-against-the-board">Playing against the board</h2><p>Compared to for example video games, the opponent is very static: it's basically the rules and the randonmness. In a co-op video game (let's say for example a shooter game), every player needs to perform on their own and only the goal is shared and strategy discussed. It's practically impossible for other players to play for you.</p><p>However, with (most) board games, there's no similar dexterity element. There's only decisions made and the knowledge, experience and logical thinking that leads to those decisions. And they can be played for other players.</p><p>And that's the biggest problem I have with the genre: if you are playing a co-op game and you aim to win, the best player can (and should) lead the game on everyone's turn. Players discuss what actions they should take and it would be a bit foolish to withhold knowledge and information if such exists.</p><p>Some games combat this with variety of techniques.</p><h3 id="no-talking">No talking</h3><p>First two techniques are very arbitrary and artificial and in my opinion don't contribute to a great gaming experience.</p><p>Games like <a href="https://boardgamegeek.com/boardgame/98778/hanabi">Hanabi</a> (the best pure co-op game in my opinion) <strong>prohibits all discussion between players</strong>. You're not allowed to talk about strategy or your plans or what someone should do. And that does solve the problem but it introduces an even larger problem: board gaming is a social activity. When I get together with my friends to play, the last thing I want to do is sit in silence.</p><p>Other games like <a href="https://boardgamegeek.com/boardgame/174430/gloomhaven">Gloomhaven</a> <strong>prevent players from discussing specific information</strong> (in this case, names and initiative values of cards) but not general strategy. In games like these however, I often see players circumvent the rules by vaguely discussing it ("I have a large number" instead of "I have a 80" or "I can take care of these two monsters from distance" instead of "I have Archery").</p><p>In addition to these hindering discussion, my main problem with this is that it's so arbitrary. And often leads to problems in defining what is actually allowed and not because you can pretty fast get to the point where you can talk about the cards without actually talking about the cards when you discuss strategy and next steps. Unless you go all Hanabi on it.</p><h3 id="randomness">Randomness</h3><p>Some games like <a href="https://boardgamegeek.com/boardgame/65244/forbidden-island">Forbidden Island</a> just make the game <strong>very random-heavy</strong>. It leads to the game being challenging even with one experienced player but in my experience just leads to the situation more often than not, that the result of the game is less about your skills and more about the RNG. Aforementioned Hanabi also falls into this category which is one of the things I don't like about it.</p><p>My experience with <a href="https://boardgamegeek.com/boardgame/30549/pandemic">Pandemic</a> is similar: the game is hard but not for the right reasons. Anytime there's a situation where even optimal play would not lead to win, I think the game has failed and my gaming experience is somewhat ruined.</p><p>I'm not completely against randomness because I think it brings interesting variety and makes it so that games don't just become chess but if due to the randomness, the game creates an unwinnable situation, it's too much.</p><h3 id="hidden-identities-personal-goals">Hidden identities / personal goals</h3><p>Another way to try to solve this is by introducing <strong>hidden personal goals</strong>. These are given to players at the beginning of the game or scenario and cannot be discussed or mentioned but can lead to conflict between players in the same team. Gloomhaven does this on a scenario basis. <a href="https://boardgamegeek.com/boardgame/150376/dead-winter-crossroads-game">Dead Winter</a> has this mechanic and I've been told you can play it as a pure co-op game with them (it usually otherwise falls into the next category). </p><p>To take a step further, there are games that are co-op only on the surface and they suddenly turn my least favorite genre into my favorite genre: the traitor mechanic. <a href="https://hamatti.org/posts/hidden-identity-in-table-top-games/">I've written earlier a more in-depth look into hidden identity games</a> but here the idea is that you don't know who is your friend and who is working against you. The players still discuss and strategize together but since you can never trust a player, everyone is required to make their own judgement calls at the end. My favorite in the category is <a href="https://boardgamegeek.com/boardgame/37111/battlestar-galactica-board-game">Battlestar Galactica: The Board Game</a>.</p><h3 id="unique-roles">Unique roles</h3><p><a href="https://boardgamegeek.com/boardgame/247694/team3-pink">TEAM3 games</a> solve the problem in a very different way: by creating <strong>different unique roles</strong> and making it impossible to play anyone else's. In TEAM3, your team needs to create a physical structure from differently colored and shaped blocks. But the twist is this: the player building cannot see, the player who knows what is to be built cannot talk and the third player needs to be the communicator between the two.</p><p>It only works for certain types of games but it does ultimately solve the issue.</p><h3 id="time-constraint">Time constraint</h3><p>Bringing in <strong>real-time constraints</strong> is one more way to battle the issue of all-knowing player. Games like <a href="https://boardgamegeek.com/boardgame/113294/escape-curse-temple">Escape: The Curse of the Temple</a> or <a href="https://boardgamegeek.com/boardgame/38453/space-alert">Space Alert</a> are co-op games where time is limited and always running. It creates an environment where you just simply don't have time to fully discuss everything but you can still benefit from the group-thinking if you're fast enough to discuss.</p><h2 id="just-agree-that-everyone-plays-on-their-own">"Just agree that everyone plays on their own"</h2><p>The other side of the coin in this discussion is that the gaming group could just decide not to do it. They may discuss strategies but everyone eventually makes their own decisions.</p><p>Maybe it's the competitive side in me but I just see this as another arbitrary limitation, this time not set by the game but by the players. If you can make an optimal move as a team, I think you always should.</p><p>I would really want to like co-op games but pretty much every time, I run into these problematic aspects of them and end up rather playing something different. I'm not sure if it's a design problem or just something that is inherent to board gaming that makes co-op games nearly impossible to design well.</p>
45 stars, Christmas was almost saved - Advent of Code retrospective
2021-12-31T00:00:00Z
https://hamatti.org/posts/advent-of-code-2021-retrospective/
<p>Advent of Code 2021 is over and I not only got a major new personal record, I got very close to my first completion. Here's a retrospective of the things I learned and/or thought during the 25 days of Advent of Code.</p><p>My toolchain of choice for this year was <a href="https://jupyter.org/">Jupyter Notebook</a> with <a href="https://www.python.org/">Python</a> and instead of writing daily blog posts on this blog, I kept writing lightweight blog-style commentary into the notebooks together with the solutions and shared them in <a href="https://github.com/Hamatti/adventofcode-2021">GitHub: hamatti/adventofcode-2021</a>. I'll migrate them to this website once I manage to decide what kind of sub-site I want to make for them.</p><h2 id="i-love-the-lore">I love the lore</h2><p>Helping Santa and the elves to save Christmas. Bingo playing giant squid, glowing lanternfish, crabs in their tiny submarines and so on. Year after year I think Advent of Code is a masterpiece in puzzle lore.</p><p>And the lore is lovely in a way that it's non-intrusive when it comes to solving the puzzles. It just adds a little something that you can smile at when you start your daily puzzle solving routine and then forget until you solve it and advance in the story.</p><h2 id="python-is-a-fantastic-language">Python is a fantastic language</h2><p>After last year of trying to learn a new language while solving these puzzles, this year I decided to pick a language I'm very comfortable with and that I enjoy.</p><p>And oh boy I've been enjoying working with Python this December. I spent quite a lot of time reading the standard library docs for <a href="https://docs.python.org/3/library/itertools.html">itertools</a>, <a href="https://docs.python.org/3/library/functools.html#functools.cache">functools</a>, <a href="https://docs.python.org/3/library/collections.html">collections</a> and <a href="https://docs.python.org/3/library/re.html">re</a> and all of them are excellent libraries.</p><p>For puzzles like these, I really like the flexibility and dynamic nature of Python. It makes it easier to quickly flesh out the code that takes an input and produces an output. </p><p>Occasionally (like in <a href="https://adventofcode.com/2021/day/18">day 18</a>), I abused the flexible type system by returning different things based on the situation. Probably not the best thing to do in real life but combined with the structural pattern matching that came with Python 3.10, it allowed me to craft a solution that I might not have otherwise done.</p><h2 id="a-good-spec-is-worth-its-weight-in-gold">A good spec is worth its weight in gold</h2><p>The creator of Advent of Code has improved his spec-writing skill immensly over the years and I thank for that. In 2021, the quality and understandability of the puzzle descriptions and the examples provided was top notch.</p><p>And it reminded me of how bad the real life software projects often are. It would be so nice to pick up a ticket from the backlog, have this level of descriptions and get working. But no. Often it's "X is broken" level of tickets that take at worst days to even understand what the problem actually is.</p><p>Sure, for artificial puzzles it's easier to define the "definition of done" but even if that's bit unclear, providing enough information in the ticket is something I think we as the industry need to become better at.</p><p>A key to these in my opinion is having a good set of examples that really help to make sure that you've understood the problem correctly.</p><h2 id="tree-structures-are-hard-to-debug">Tree structures are hard to debug</h2><p>My biggest struggle throughout the month was the multiple puzzles that called for a tree structure and recursion to solve. I'll admit, I'm not great with trees.</p><p>I learned about them in the university a decade ago and can reason with the theory part but implementing it in code is a different challenge. I did improve a lot in my ability to do that but there was one major thing I realized during this time:</p><p>My workflow is very debug-driven – and my current toolkit for debugging doesn't translate well to trees. So many times I had relatively simple bugs that I struggled to figure out because I couldn't debug like usual.</p><p>In non-recursive, non-tree solutions I can pretty much always say "I want to inspect the state at this given point" but when traversing trees with recursion, I can't and the amount of times functions are run even on rather smaller trees is so large that any print-debugging just fills the screen.</p><h2 id="so-many-ways-to-solve-and-options-to-focus-on">So many ways to solve and options to focus on</h2><p>One thing I absolutely love about Advent of Code is that it offers a way to decide on your own what you want to focus on. Last year for me it was learning Rust. This year it was working on improving my writing. For some, it might be getting high scores on the leaderboard and for others, writing the most performative code they can.</p><p>For me, learning is always the key. I didn't have any hard rules for this year but I ended up solving all of them without any libraries outside the standard library (and my own utils library for reading inputs). But if I'd have decided one day to pick up a library because it would have been a good opportunity to learn it, I would have.</p><h2 id="learning-with-friends-is-awesome">Learning with friends is awesome</h2><p>Another aspect of Advent of Code that I like and have said on probably every post I make about it: community is great. So many developer communities are buzzing during the month of December which offers lots of peer support, opportunities to learn and someone to rant with when things are hard.</p><p>And part of that is sharing my own solutions with people and getting feedback and learning more based on them.</p><p>There's also a very active sub-reddit at <a href="https://reddit.com/r/adventofcode">r/adventofcode</a> but I've never been a redditor so I've only gone there a few times to check the memes. People also share a lot in blogs and social media so searching for "Advent of Code" on those platforms or Google is a great a way to learn more material.</p><h2 id="puzzle-solving-skills-development-skills">Puzzle solving skills != development skills</h2><p>Solving puzzles like these can be frustrating. I've never used tree structures in my day-to-day work as a developer nor have I needed any math tricks to get through my work. Outside job interviews' bullshit technical trivia questions and puzzles, this kind of skillset is not most often needed.</p><p>So if you're like me and really struggle getting through some of these puzzles, don't worry. Some of the best developers I know are horrible at solving puzzles but awesome at building usable and accessible products that solve real-world problems and are delight to use.</p><p>Puzzle solving (just like for example <a href="https://codegolf.stackexchange.com/">Code Golfing</a>) is a very specific sub-category of software development. Working on those puzzles can be rewarding and it can really help hone the basic skills. I can't remember when I've coded for 25 days straight before, working on the fundamentals but this month I did and learned a ton.</p><h2 id="i-got-a-bit-addicted">I got a bit addicted</h2><p>One thing that happened midway through was that I got bit addicted on these and started solving the first Advent of Code from 2015 as well. In 3 days, I managed to get 44 stars and I'm planning to finish those last 6 one day too.</p><p>And I might do the other years as well, especially now that I've enjoyed the Jupyter Notebook + Python combo quite a lot this year. Would be cool to have a full collection of 350 stars together with full written commentary on each puzzle.</p><p>But it's also very likely that I don't want to see another puzzle for another year now that I'm through.</p><h2 id="day-19-was-the-hardest">Day 19 was the hardest</h2><p>Despite me saying in the beginning that the tree puzzles were one I struggled with most, <a href="https://adventofcode.com/2021/day/19">day 19</a> was the hardest one for me. Not because the solution to it would be that hard to implement (contrary to those trees) but because I had hard time understanding the math related to it. 3D geometry is not something I've done since high school 15+ years ago so understanding how to solve it was such a challenge.</p><p>The other one I failed to do was the 24th, mostly because it was the Christmas Eve and it was much more important to spend the day with the family than coding. I got close enough on that day to know that I could have solved it with a bit more time.</p><h2 id="so-close">So close</h2><p>All in all, I'm very happy with this year's result. But also, getting so close to the 50 stars and not managing to complete it is slightly annoying the perfectionist in me. But given that my previous record from 2020 was 15 stars, 45 is a huge deal.</p><p>I also had some ideas for the community side that didn't get done this year that I'll keep in my notes for 2022 so there would be something new coming also next year.</p><p>Now it's time to wrap up 2021 and head over to the new year.</p>
Year in Review 2021
2021-12-29T00:00:00Z
https://hamatti.org/posts/year-in-review-2021/
<p>
Last year, in my
<a href="https://hamatti.org/posts/year-in-review-2020/">Year in Review 2020</a>, I wrote: "For 2021, my only hope is that we manage to get rid of this damn
pandemic." Yeah, that didn't happen. But other things did happen this year so
let's take a look at those.
</p>
<p>
This blog post is primarily written for future me. I like documenting my
journey and have found going back to earlier years' review notes really
enjoyable. However, you're free to join the ride! I hope this post will open
up to you a bit more about who I am beyond all the tech and community posts
and board game projects. I've also learned a lot about my personal growth from
the evolution of my writing style and the way I do retrospections.
</p>
<p>
If you're interested in doing something similar – even if just for yourself –,
there's a great tool called
<a href="https://yearcompass.com/">Year Compass</a> that I've used since 2015
to take a look back and to prepare for the following year.
</p>
<p>
Previous ones are here:
<a href="https://hamatti.org/posts/year-in-review-2016/">2016</a>,
<a href="https://hamatti.org/posts/year-in-review-2017/">2017</a>,
<a href="https://hamatti.org/posts/year-in-review-2018/">2018</a>,
<a href="https://hamatti.org/posts/year-in-review-2019/">2019</a>,
<a href="https://hamatti.org/posts/year-in-review-2020/">2020</a>.
</p>
<h2 id="pandemic-ridden-2021">Pandemic-ridden 2021</h2>
<p>
Unsurprisingly, the overarching theme of 2021 was the pandemic. After a rough
2020, this year wasn't much better. From January to end of April, it was all
the more the same: sitting at home alone, trying to make some things happen
but mostly just not doing anything. After the vaccination rollout started
happening, I took my first trip in a year to visit home and spent May and most
of June at my childhood home, working from remotely and starting my summer
holidays.
</p>
<p>
It was a nice change of scenery: being able to go to sauna on a weekly basis,
being able to barbeque food and sit outside next to the nature to eat food,
listen music and even do video meetings. By the time my holidays started in
mid-June, I was so ready for a break and it felt good for a few months. The
pandemic situation was easing up a bit for the summer and there was a glimmer
of hope.
</p>
<p>
During the summer I also got to meet some friends in Turku to play board games
and eat dinners and life felt good for a moment.
</p>
<p>
After getting back to work, first few months were more of the same: feeling
inadequate, having a lot of stress, lack of sleep and worrying if I'd ever be
able to bounce back and be able to work again – or if anyone would even want
to have me work for them.
</p>
<p>
In late September, Finland started easing up on restrictions and opening up
and I did my first in-person workshop in roughly 1.5 years and it felt so
good. In October the policies at work changed too and all together in October
and November I did 20+ events, workshops, talks and other community
gatherings. It felt amazing.
</p>
<p>
And then in late November, things went south again, we closed the office again
and I was back to canceling all the events planned for December. As I had
feared earlier, my mind couldn't really handle that and I've been on a sick
leave ever since after crashing real bad. Christmas stress didn't make that
any easier though.
</p>
<h2 id="the-things-that-did-happen">The things that <em>did </em>happen</h2>
<p>
For almost 2 years now, I've been in the mode of desperately trying to
reinvent myself and the way of doing what I do in this new situation and
mostly it's been failure after failure. I still tried a few things, some that
made me happy for a while.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/codebase-banner.png" class="kg-image" alt="codebase season 1" />
</figure>
<h3 id="codebase">codebase</h3>
<p>
For a while, predating even the pandemic, I've had this interest in
cross-pollination of ideas across different developer communities. I often see
people mostly reading and learning and participating in communities directly
related to the technologies they are working with. I personally believe
there's a lot to learn from these other technologies and their communities.
</p>
<p>
In March, I launched <a href="https://hamatti.org/codebase">codebase</a>, a
Youtube live show that celebrates developer culture and attempts at this idea
sharing. I managed to do 4 livestreams and had to cancel 5 others due to lack
of energy, scheduling issues and general pandemic anxiety. Those 4 streams
ended up being great though. We talked about
<a href="https://www.youtube.com/watch?v=vUArlJgzQ9M">web accessibility with Fotis</a>,
<a href="https://www.youtube.com/watch?v=7q6udGF6a28">Clojure backend development with Ykä</a>,
<a href="https://www.youtube.com/watch?v=qxwLdvdl04s">the history and modern times of PHP with Larry</a>
and
<a href="https://www.youtube.com/watch?v=kd6XtCPEpsA">the wonderful world of web components with Matias</a>.
</p>
<p>I'd love to continue codebase in 2022 with a second season.</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/blogging-banner-1.png" class="kg-image" alt="Writing" />
</figure>
<h3 id="writing">Writing</h3>
<p>
As you're reading this in my blog, you might have already noticed or guessed
that I like writing blog posts. In October, I reached 1 year of weekly
blogging, having published at least one blog post each week for 52 straight
weeks and I've been continuing regular blogging ever since.
</p>
<p>Here are my favorites from this year:</p>
<ul>
<li>
<a href="https://hamatti.org/posts/you-should-start-a-blog-today/">You should start a blog today</a>
(Jan 2021)
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust series</a>
(Feb-Sep 2021)
</li>
<li>
<a href="https://hamatti.org/posts/learning-in-public/">Learning in public</a>
(Feb 2021)
</li>
<li>
<a href="https://hamatti.org/posts/how-to-parse-command-line-arguments-in-python/">How to parse command-line arguments in Python</a>
(Mar 2021)
</li>
<li>
<a href="https://hamatti.org/posts/why-scheduling-slack-messages-and-emails-is-so-valuable-for-community-builders/">Why scheduling Slack messages and emails is so valuable for community
builders</a>
(Jun 2021)
</li>
<li>
<a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed/">Your blog should have an RSS feed</a>
(Aug 2021)
</li>
<li>
<a href="https://hamatti.org/posts/roll-and-write-board-games-are-awesome/">Roll and Write board games are awesome</a>
(Sep 2021)
</li>
<li>
<a href="https://hamatti.org/posts/tiny-handy-tools/">Tiny handy tools</a>
&
<a href="https://hamatti.org/posts/tiny-handy-tools-community-edition/">Tiny handy tools: Community Edition</a>
(Oct 2021)
</li>
<li>
<a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/">Minimal Travel Table Top Game Collection 3: Project 108 </a>(Dec 2021)
</li>
<li>
<a href="https://hamatti.org/posts/tips-for-advent-of-code/">Tips for Advent of Code</a>
(Dec 2021)
</li>
</ul>
<p>
Also,
<a href="https://cult.honeypot.io/reads/my-employer-supports-my-open-source-contributions/">my article on open source and Spice program was published in Honeypot's
.cult</a>
in May which was awesome. It was the third article of mine published there.
</p>
<p>
One writing project I didn't manage to finish this year either is my long
on-going Humane Guide to APIs which I'm writing to help junior frontend
developers to gain a solid practical grasp of using APIs. Every now and then
I've gotten back on it and done some writing but I still struggle with some of
the structural parts of it. Maybe in 2022 it'll see the light of day.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/speaking-banner.png" class="kg-image" alt="Speaking" />
</figure>
<h3 id="talking">Talking</h3>
<p>
I also did a bunch of online talks/workshops and got to do a few in-person as
well during that small gap.
</p>
<p>
Some major highlights were lecturing in Aalto University, speaking in
TEDxKuopio in June and in DevRelCon in November. All in all, I finished the
year with 28 talks/podcasts/workshops/lectures/etc.
</p>
<p>
Speaking in in-person events is so so so much more enjoyable and rewarding
that I can't wait to get back to meetups and conferences, hopefully in 2022.
</p>
<p>
One of the most exotic speaking engagements this year was speaking in Rust
Denver as my talk was at 3.30am local time mid-June, during the time in
Finland when the sun doesn't really go down.
</p>
<p></p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/coding-banner-1.png" class="kg-image" alt="Coding" />
</figure>
<h3 id="software">Software</h3>
<p>
I also built a few software projects during 2021. I enjoy programming more
these days than ever now that I don't anymore do it for professionally. I get
to work on stuff I enjoy, experiment with technologies I want to learn, write
and talk about my projects and not worry about deadlines.
</p>
<h4 id="235">235</h4>
<p>
<a href="https://hamatti.org/posts/introducing-235/">235</a> was my first-ever
Rust project and it has been in daily use ever since January when I got the
first version published. Getting NHL results in the command line with no extra
clutter is just the perfect experience for myself and building that while
learning a new language was one of the highlights of 2021.
</p>
<h4 id="pokemon-tcg-gym-leader-challenge-deck-validator">
Pokemon TCG: Gym Leader Challenge Deck Validator
</h4>
<p>
Another highlight is
<a href="https://github.com/Hamatti/gym-leader-challenge-deck-validator">Gym Leader Challenge Deck Validator</a>
that I built for the Pokemon TCG community as a new community-born format was
created and started gaining traction.
</p>
<h4 id="taajuus">Taajuus</h4>
<p>
At the start of the year I built
<a href="https://github.com/Hamatti/taajuus">Taajuus</a>, which is an
open-source web adaption of a great
<a href="https://boardgamegeek.com/boardgame/262543/wavelength">board game Wavelength</a>. I built it after I had promised to organize fun activities for our team's
bi-weekly session and couldn't find an online version of the game that I
liked. It's built to be played over video calls and it turned out to be a lot
of fun. Speaking of work and fun, I also hosted a murder mystery dinner game
for a team in our Tampere office and got some experience in hosting games from
that.
</p>
<h4 id="advent-of-code">Advent of Code</h4>
<p>
In December, I finally had enough time on my hands to fully participate in
<a href="https://adventofcode.com/">Advent of Code</a>, focusing this year on
improving my writing and my core Python skills. I picked up Jupyter Notebook
as a tool to combine puzzle solving and blog-style writing into one and kept
publishing my solutions with commentary on my GitHub page at
<a href="https://github.com/Hamatti/adventofcode-2021">hamatti/adventofcode-2021</a>. I had a lot of fun doing these together with friends from multiple
communities. My fellow developer people at Futurice, Koodiklinikka, Asteriski,
Digit and Lumberchill definitely made this year's Advent of Code more fun that
it had ever been.
</p>
<h4 id="other-smaller-projects">Other smaller projects</h4>
<p>
I also did a bunch of smaller customization projects like
<a href="https://hamatti.org/posts/customizing-liiga-fi-experience/">making Liiga.fi site more usable</a>, worked on my unpublished work-in-progress Pokemon TCG Online Cube Draft
tool, experimented with Svelte on another unpublished sport stats project and
<a href="https://hamatti.org/posts/syntax-highlight-all-the-things/">built a Prism.js syntax highlight extension for Pokemon TCGO deck lists</a>.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/communities-banner.png" class="kg-image" alt="Communities" />
</figure>
<h2 id="communities">Communities</h2>
<p>
<a href="http://turkufrontend.fi/">Turku <3 Frontend</a> and Helsinki Dev
Lunch were mostly on a hiatus during the year, although in the fall we managed
to run a couple of great events bringing the community together until we had
to shut down everything again.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/dev-lunches.png" class="kg-image" alt="Four pictures of people eating lunch with texts Oulu, Tampere, Helsinki and Turku on top of images in that order" />
</figure>
<p>
One of the things I got to enjoy during the short window of events was eating
lunches with developers in Oulu, Tampere, Helsinki and Turku within a month.
Eating together is a great way to connect with people since most everyone eats
and I've found it a nice way for even more introverted of us to join as you're
not expected to do other things than to eat and can participate in discussion
on your own pace.
</p>
<p>
In May, I was involved in organizing
<a href="https://euruko2021.org/">Euruko</a> conference as a Discord
moderator, and in November I joined
<a href="https://react-finland.fi/">React Finland's</a> organizing team to
help organize next year's conference.
</p>
<p>
<a href="https://koodiklinikka.fi/">Koodiklinikka</a>'s online community was a
huge part of my year as well and in the spring I ran a community-driven salary
survey to help members understand what kind of salaries others are making in
Finland on different levels of experience and positions.
</p>
<p>
I also started planning a new kind of meetup-ish community around technical
documentation that I hope might see day of light next year if we're able to
get back to organizing in-person events.
</p>
<p>
I also got to mentor a few junior developers both 1on1 and through
<a href="https://codebar.io/">codebar</a> and that is always something that
brings so much joy to my life. I wish I could do that half-time for a living.
</p>
<p>
I got to also see a lot of friends shine this year. People who started doing
more community stuff and public speaking and blogging being noticed and
brought into meetups and conferences and being awarded for their amazing work.
Love it ❤️. Also seeing friends find their first jobs in the industry is
lovely.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/futurice-banner.png" class="kg-image" alt="At Futurice" />
</figure>
<h3 id="at-work">At work</h3>
<p>
At Futurice, two major things that we kept running throughout the year were
our monthly
<a href="https://futurice.com/devbreakfast">developer newsletter Dev Breakast</a>
and our internal weekly
<a href="https://futurice.com/techweeklies">tech meetup Tech Weeklies</a>.
Both of them are things I'm extremely proud of but the effects of the pandemic
were definitely visible also on how much (or how little) we had people
creating talks and curating the newsletter – understandbly so.
</p>
<p>
I also took time this year to call every new developer who started at the
company between January and October, which turned out to be a great way to get
to know the new members of our internal developer community and to make some
really good friends across Europe.
</p>
<p>
I was also part of a team that won one of our remote company pub quizzes that
our offices in Germany were running. It was a nice way to connect more with my
colleagues in Germany.
</p>
<p>
I participated in our FutuStories campaign sharing
<a href="https://futurice.com/blog/futustories-five-things-juhis-has-learned-about-tech-communities">my insights into developer communities</a>
and worked on our new Futurice Academy trainee program as well as our summer
job programs both from this last summer and for next summer.
</p>
<h2 id="a-lot-of-soul-searching">A lot of soul searching</h2>
<p>
The worst part of this pandemic has been that I feel like I've lost myself
into this never-ending nightmare. After finally reaching a situation both
professionally and personally where I thrived in late 2019/early 2020, having
to give up all of that and not being able to make things happen for almost two
years already has done some major damage.
</p>
<p>
For years and years I've been trying to find my place in all this. What I
really want to do is to help people get into technology but I still feel I'm
very much early in that journey trying to discover what's the best way for me
to do it. And the pandemic has definitely hindered my abilities to experiment
and has caused a lot of stress and anxiety, especially when combined with the
social media where the only thing that seem to matter is if you please the
algorithm enough to get your stuff out there. And I flat out decline to do
that.
</p>
<p>
I also faced a lot of rejections this year, all throughout the 12 months. Some
were harder to accept than others and all of those really started to take
their toll on my mental well-being. And I had to give up one possible dream in
December when my mental health took a plunge to the worse. That sucks as I had
waited for 3 years for the opportunity to arrive.
</p>
<p>
Not being able to make progress, and not being able to keep the things I've
started running has been rough.
</p>
<p>
I've cried more this year than probably any other years since I was a toddler.
So much cosmic anxiety around.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/12/2022-banner.png" class="kg-image" alt="2022?" />
</figure>
<h2 id="what-s-up-future">What's up, future?</h2>
<p>
Honestly, looking into the future and dreaming of the next things to come used
to be my favorite part in these year in review notes. But this year it's not.
It feels like I can't afford any optimism or hope because the more I dream,
the harder I crash when the rug is pulled away from my feet.
</p>
<p>
But I'll allow myself to dream an unlikely optimistic future today. If not for
anything else, at least for the sake of "if it happens, I'll have a list of
things to do".
</p>
<ol>
<li>
I have this idea for a meetup community that doesn't have its own meetups.
Bit like
<a href="https://blackhops.com.au/gypsy-brewing/">gypsy breweries</a> don't
have their own breweries or
<a href="https://www.thefoodcorridor.com/2019/12/05/everything-you-need-to-know-about-cloud-kitchens-aka-ghost-kitchens-in-2020/">cloud kitchens</a>
don't have their own restaurants. I want to start a
<em>nomad meetup </em>themed around technical documentation. Communities
like <a href="https://www.writethedocs.org/">Write the Docs</a> are great
but I'm bit afraid they attract people already into documentation and I'd
like to spread the good word to other developers as well. The idea is to
gather a pool of speakers who want to talk about documentation and then
co-host events with existing meetup communities by providing speakers to
talk about docs and technical writing.
</li>
<li>
Play more board games. Way more. During these two years, I've pretty much
only played during midsummer and during Christmas break when I've been able
to visit my usual crew in Turku. I've spent more time in
<a href="http://boardgamegeek.com/">BoardGameGeek</a>'s forums,
<a href="https://www.youtube.com/c/NoRollsBarred">No Rolls Barred's Youtube channel</a>
and
<a href="https://hamatti.org/tabletop">designing my own board game projects</a>
than actually playing games and it honestly makes me very sad because I love
this hobby.
</li>
<li>
Travel to Europe. I want to meet all the new friends I've made online during
these years and to reconnect with the ones I met before the pandemic in
meetups and conferences. Tallinn, Stockholm, Copenhagen, Berlin, Munich,
Prague, Amsterdam, Brussels – all of them I keep dreaming of. I also want to
test the new overnight train from Stockholm to Berlin as that will make my
non-plane traveling so much easier.
</li>
<li>
Codebase season 2 needs to happen. So many preliminarily booked guests and
topics were left over from 2021 that I want to refine the concept and bring
more different aspects and communities to the mix and to highlight great
people doing good stuff.
</li>
<li>
Write more. I really want to improve as a writer so in 2022, I want to keep
up my monthly blogging schedule but also finish a few bigger pieces, mainly
the aforementioned Humane Guide to APIs but also a few guest posts I already
kinda promised to provide some day. I'd also like to make time to explore
fiction writing again as I have some stories in my head I'd like to flesh
out. I also have a 14-part CSS blog series in works that hopefully will move
forward a bit next year.
</li>
</ol>
Merry Christmas!
2021-12-22T00:00:00Z
https://hamatti.org/posts/merry-christmas-2021/
<p>The holiday season and Christmas is just a few days away which means this Wednesday it's time to start slowing down. Hence, I wish you all a very merry Christmas.</p><p>Next week I'll share my annual Year in Review retrospective of the horrible 2021 and after that, it's time for a new year and new technology and community blog posts – each Wednesday as usual.</p><p>Now, grab a mug of glögg or any other seasonal drink you enjoy and enjoy the holidays.</p>
Tips for Advent of Code
2021-12-15T00:00:00Z
https://hamatti.org/posts/tips-for-advent-of-code/
<p>Second week of Advent of Code is in the books! If you wanna check my solutions, they can be found at <a href="https://github.com/Hamatti/adventofcode-2021">hamatti/adventofcode-2021</a> with commentary and documentation.</p><p>As I've been solving this year's <a href="https://adventofcode.com/">Advent of Code puzzles</a>, discussing them with others, reading other people's solutions and helping with a lot of juniors with debugging and solving issues, I've noticed a few patterns that I think can make your puzzle solving easier. Some of these tips are great for other programming too outside puzzle solving.</p><h2 id="start-with-the-example-input">Start with the example input</h2><p>One thing that makes Advent of Code a premium puzzle challenge platform is the quality of specs and example inputs and outputs (not to mention the captivating lore!). For simpler puzzles, they provide a single input-output combo that you can test towards but for more complex ones (like <a href="https://adventofcode.com/2021/day/12">Day 12, 2021</a>) they provide multiple sets of examples and for some (like <a href="https://adventofcode.com/2021/day/6">Day 6, 2021</a>) they provide output examples for different steps of iteration.</p><p>My recommendation is to always build your solution with this input. The real puzzle input is usually so large that it's near impossible to manually verify steps but with these contrived examples that's definitely doable.</p><p>This is one piece of advice that I've been bit lazy with <a href="https://github.com/Hamatti/adventofcode-2021">this year</a> to be honest. After writing this blog post, I refactored my utility function to have support for loading example data (named <code>day_[nro]_example.txt</code> compared to <code>day_[nro].txt</code> for real input).</p><h2 id="utility-functions-to-read-inputs">Utility functions to read inputs</h2><p>This one is something I do very diligently to make puzzle solving less worrysome but that I don't often see others do that often.</p><p>Each day, you're required to read in an input from a file and then perform some computation on the data. This means that 25 times during December, you're essentially doing the same thing over and over again.</p><p>I like to hide that into a separate, reusable part of my codebase each year. Most of the days, the input is one-input-per-line:</p><pre><code class="language-bash">15
14
16
71
304</code></pre><p>for which I have a <code>read_input</code> function in my <a href="https://github.com/Hamatti/adventofcode-2021/blob/main/src/utils.py">utils.py</a> that accepts a <code>transformer</code> function that is run on each line (for example in the above, I'd run <code>int</code> to turn everything into numbers before it arrives to my puzzle solver).</p><p>Some days, you'll see a multisection input (sections separated by empty line):</p><pre><code class="language-bash">KHSNHFKVVSVPSCVHBHNP
FV -> H
SB -> P
NV -> S
BS -> K
KB -> V
HB -> H
NB -> N
VB -> P</code></pre><p>for which I have <code>read_multisection_input</code> function in <a href="https://github.com/Hamatti/adventofcode-2021/blob/main/src/utils.py">utils.py</a> that accepts a list of transformers to transform each section to a format I want it to be.</p><p>And some days, the input is just too custom for a generic solution so I write a custom input reader.</p><p>Doing it like this achieves two things:</p><ol><li>I don't have to think about parsing each day</li><li>I get the data in the right format so I can have higher confidence level that my parsing is not the cause of bugs</li></ol><p>I also like that it hides implementation details from the puzzle solving to a utility library.</p><h2 id="assertions-to-protect-from-later-mistakes">Assertions to protect from later mistakes</h2><p>If you're doing proper automated tests for your puzzles, then this can part can be skipped. But for most, unless doing tests is what you're focusing on as a technique, it's quite likely people are not doing full on TDD on these puzzles.</p><p>Most scripts print out the final result into terminal and then copy-paste from there to the website.</p><p>But then you go towards the second part or you do some refactoring to clean up your code and change things in your functions. Suddenly, it might be that the result is no longer correct.</p><p>After I get a correct result, I like to add an assert like this:</p><pre><code class="language-python">result = solve(input)
assert result == 102
print(f'Solution: {result}')</code></pre><p>next to my print call. This way, if I change something that causes incorrect behaviour, my code will throw an uncaught error and break.</p><p>Just the other night I was helping a friend debug their part 2 solution for a puzzle and it turned out they had refactored something after part 1, didn't test it with part 1 again and assumed everything inside that helper function was correct because they had originally gotten the right result.</p><p>An assertion there would have saved them from a lot of trouble.</p><p>Assumptions are always the root of evil but we make them even when we try not to so let's not trust ourselves and let's make the computer keep us in check.</p><h2 id="debug-like-a-champion">Debug like a champion</h2><p>I've written <a href="https://hamatti.org/guides/humane-guide-to-debugging/">a longer piece on debugging</a> earlier so I won't go through everything here but I do want to share some here because most of you won't read the linked guide anyway.</p><ol><li>First rule of debugging is that you're never good enough not to debug.</li><li>Second rule of debugging is that you need to leave any and all assumptions at the door.</li></ol><p>Often, when people get a wrong result, I see the same behaviour (regardless of their seniority level): people keep looking at the end result and make somewhat random changes (based on educated guesses on where the problem might be at).</p><p>Looking at your code is pretty much the last thing you should be doing when you end up in debugging mode. Adding debugging statements or print calls is the way to go: your priority number 1 is to find out where things start going wrong.</p><p>If you have split up things into functions, verify (not just by reading the code but by printing out values (or by writing tests)) that each of those function correctly. Until you've done that, you don't know they do.</p><p>Then, instead of running the required 20, 40 or 100 iterations, only run one and see if you're getting a verifiably correct result. Then run the next iteration only and see if it still holds.</p><p>Create a smallest possible example you can to test out a single part of your code. Run only that and not the entire thing. We don't care about the result of the puzzle solver at this point. We just care that individual lines of code and individual functions work correctly.</p><p>Share inputs with friends: it's sometimes helpful to ask a friend to run their correct solution with your input to double check that the input is good; or to run your friend's input with your code to see where the solution differs.</p><h2 id="printer-helpers-for-grids">Printer helpers for grids</h2><p>Some of these puzzles deal with grid systems and coordinates. Inside the code, these are often computed with <code>(x, y)</code> points and/or lists of lists (or numpy arrays in Python).</p><p>While it's great system for a computer, it's hard to see if </p><pre><code class="language-python">[[True, False, True, False, False], [False, False, True, False, True], [True, False, False, True, True], [True, True, True, True, False], [True, False, True, False, True]]</code></pre><p>is what we want our grid system to be.</p><p>Building a helper function just for printing out the grid for debugging purposes is a great approach:</p><pre><code class="language-python">def print_grid(grid):
for row in grid:
for col in row:
if col:
print('#', end='')
else:
print('.', end='')
print()
print_grid([[True, False, True, False, False], [False, False, True, False, True], [True, False, False, True, True], [True, True, True, True, False], [True, False, True, False, True]])
## Prints:
# #.#..
# ..#.#
# #..##
# ####.
# #.#.#</code></pre><h2 id="split-your-code-into-smaller-functions">Split your code into smaller functions</h2><p>Often Advent of Code puzzles are such that you can write a single script with no extra functions to run the calculations and get a result.</p><p>And that's perfectly fine and good, until your code doesn't work. Because debugging a long code that does multiple things is very difficult.</p><h3 id="improves-debuggability">Improves debuggability</h3><p>A good example of this is <a href="https://adventofcode.com/2021/day/13">Day 13, 2021</a> in which we had to fold transparent paper multiple times. It's possible to run these folds without separating them into a function but testing that out becomes challenging.</p><p>Creating a <code>fold</code> function that takes an input and produces an output gives you a superpower of being able to run a single fold, irregardless of the rest of the puzzle. So you can handcraft an input, run it through <code>fold</code>, print the output and compare it with your notes.</p><p>Pure functions (meaning functions that don't have side effects and always create the same output with the same input) are really nice for that very reason.</p><h3 id="helps-with-part-2">Helps with part 2</h3><p>Over the years I've participated in Advent of Code, I've kind of learned to guess what kind of changes happen in part 2 and that influences what kind of code I write in part 1.</p><p>A good example of this is <a href="https://adventofcode.com/2021/day/9">Day 9, 2021</a> where we first find low points and then count areas of subareas. In part 1, the output is just a number of these low points but I like write functions like <code>find_low_points</code> in <a href="https://github.com/Hamatti/adventofcode-2021/blob/main/src/day_9.ipynb">Day 9</a> that return a list of those points rather than just a number of them.</p><p>So when part 2 rolls in and we need a list from part 1, I can just call that function and continue without having to rewrite the loops and operations from part 1 like I would have if I just ran flat scripts without dividing to sub functions.</p><h3 id="you-can-test-them">You can test them</h3><p>If you're running automated tests, it's nice to test not only the end result but also the individual helper functions.</p><p>This year there has been multiple puzzles where we've ended up creating a 2D coordinate system and then moving values around or doing calculations on cells and it can get complex quite fast – and it's hard to find the issues in complex code.</p><h2 id="talk-share-ask-for-help">Talk & share, ask for help</h2><p>My favorite part of Advent of Code is the social community aspect. Pretty much every developer community I'm active in is buzzing with discussions and people sharing their solutions and us debugging problems together.</p><p>Twitter is also another place where I've found a lot of good discussions, different solutions and especially <a href="https://hamatti.org/posts/advent-of-code-1-getting-started/">in 2020 when I was learning Rust</a>, I got so much help from the Rust community in Twitter when sharing my solutions and blog posts and asking for help.</p><p>When everyone is working on same problems at the same time, there's immense power in reading through other's solutions even if they are in different programming language than what you use. Since you don't have to understand a new domain or context (like you'd have to in real-life problems and solutions), it's easier to focus on the puzzle solving part.</p><h2 id="bonus-tip-write-a-blog">Bonus Tip: Write a blog</h2><p>It's not a secret that <a href="https://hamatti.org/posts/you-should-start-a-blog-today/">I'm an ambassador for developers writing blogs</a>. There's so much that you can learn from writing for others.</p><p>Last year I wrote three weekly blog posts about <a href="https://hamatti.org/posts/advent-of-code-1-getting-started/">my learning journey and struggles of doing Advent of Code with Rust</a> and this year I've been writing a few blog posts but also experimenting with writing about stuff in <a href="https://github.com/Hamatti/adventofcode-2021">Jupyter Notebooks with my solutions as mini blogs</a>. Each day, I not only talk about how I approached the problem but often share tidbits of Python knowledge about different standard library functions that are handy for Python developers.</p><p>Split between solving the puzzles and writing the other stuff around it, more than half of my time every day goes to writing and research (including testing out how things work). Each day I learn a bit more and I strengthen my basic skills with the language and programming because I need to explain them to others.</p><p>I also like <a href="https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/">how it improves my code</a>. Many of my best refactorings happen when I write and explain what my code does and then realize that there's a better way to express that same thing in code. When I write code, I'm in a functional mode trying to make things work but when I write for people, I'm in a communication mode trying to make things understandable and that really helps improve my code.</p>
Advent of Code 2021
2021-12-08T00:00:00Z
https://hamatti.org/posts/advent-of-code-2021/
<p>It's December again and like so many years before this, I've picked up <a href="https://adventofcode.com/">Advent of Code</a> to help Santa and the elves to save Christmas.</p><h2 id="what-s-advent-of-code">What's Advent of Code?</h2><p>Advent of Code is an annual programming puzzle advent calendar that's been running since 2015, created by <a href="http://was.tl/">Eric Wastl</a>. Well, actually they are not necessarily programming puzzles but most often easiest to solve by the power of computing.</p><p>Each day, you'll get a puzzle, for example from day 1, 2020:</p><blockquote>Before you leave, the Elves in accounting just need you to fix your <em>expense report</em> (your puzzle input); apparently, something isn't quite adding up.<br /><br />Specifically, they need you to <em>find the two entries that sum to <code>2020</code></em> and then multiply those two numbers together.<br /><br />For example, suppose your expense report contained the following:<br /></blockquote><pre><code class="language-bash">1721
979
366
299
675
1456
</code></pre><blockquote>In this list, the two entries that sum to <code>2020</code> are <code>1721</code> and <code>299</code>. Multiplying them together produces <code>1721 * 299 = 514579</code>, so the correct answer is <code><em>514579</em></code>.</blockquote><p>Each puzzle contains of alphanumeric input that is generated for each user and an output that is posted to the website to see if you solved it.</p><p>That variety opens up the floor to a lot of different approaches and over 100k developers solve Advent of Code puzzles every year in so many different ways.</p><p>Once you solve a puzzle, you're given a second part: often the same puzzle with a new twist that requires you to rethink your solution.</p><p>I especially love the community aspect of Advent of Code. Pretty much every developer community I'm involved it has a channel in Slack or Discord or threads in forums where people solve puzzles, showcase their solutions and discuss them.</p><h2 id="python-jupyter-notebooks-for-2021">Python & Jupyter Notebooks for 2021</h2><p>Last year <a href="https://hamatti.org/posts/advent-of-code-1-getting-started/">I took the opportunity to start learning a new language, Rust</a>. It was a nice choice as I ended up learning the basics of Rust and continuing to this year to build my first Rust program.</p><p>However, it was also very challenging because not only did I need to solve increasingly difficult puzzles, I had to learn a totally new language and eventually I ended up with 15 stars and had to give up.</p><p>This year I decided to focus on two things: learning how to use <a href="https://jupyter.org/">Jupyter Notebooks with Python</a> and focusing on writing blog/tutorial style thoughts of my solutions. I'm already comfortable coding with Python which opens up a lot of room for learning something new. I've tried Jupyter Notebooks a few times but mostly just fiddling around and wanted to see what I could do with them.</p><p>And while writing isn't a new thing for me, it's a thing I have so much to learn and room for improvement. So far, my approach has been writing down my thoughts and how I approach solving the puzzles while sharing tidbits of Python knowledge to help the reader learn a bit more about the language and its ecosystem.</p><p>I share my solutions at <a href="https://github.com/Hamatti/adventofcode-2021">hamatti/adventofcode-2021</a> in GitHub. I really like how GitHub renders the Jupyter Notebooks in a nice readable form, making it easy for anyone to follow. Once this year's Advent of Code (or my participation in it) is done, I'll migrate the notebooks under this website.</p>
Customizing liiga.fi experience
2021-12-04T00:00:00Z
https://hamatti.org/posts/customizing-liiga-fi-experience/
<blockquote><strong>Update Dec 8th:</strong><br />I noticed that when a game was live, my highlighting code at the bottom did not correctly find the right teams. I've fixed that bug and updated the code on December 8th, 2021. If you copied the code earlier than that, I recommend copy-pasting it again to get the bug fix.</blockquote><p>As a sports fan, there are three main things I'm interested in when I visit a sports organization/league's website:</p><ol><li>Current games with scores / next upcoming games</li><li>Standings</li><li>Tournament system (how many games, how to advance, what's tiebreaker etc)</li></ol><p>However, most sports websites are not very good at showing these so I must be in some kind of minority in their (perceived/assumed) audience.</p><p>Luckily, web is a platform that can be customized a lot in many different ways. One of these ways is to run custom CSS using tools like <a href="https://github.com/openstyles/stylus">Stylus</a>.</p><h2 id="liiga-fi-before">Liiga.fi before</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/12/Screenshot-2021-12-04-at-14.11.58.jpg" class="kg-image" alt="Screenshot of Liiga.fi with ad banner at top and lots of article/video pieces taking most of the space" /></figure><p>Here's a screenshot of Liiga.fi front page on Saturday, December 4th at 14:13. Ads, news, videos and luckily with their newest update, the scores on the left easy to find!</p><h2 id="liiga-fi-after-custom-css">Liiga.fi after custom CSS</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/12/Screenshot-2021-12-04-at-14.01.38.png" class="kg-image" alt="Screenshot of Liiga.fi showing only current games and standings next to each other" /></figure><p>I really only care about the scores and the standings. Now I don't need to scroll or find the information I want from all the extra stuff.</p><h3 id="that-custom-css">That custom CSS</h3><pre><code class="language-css">#main-partners-banner-container {
display: none;
}
#main-right-container {
display: none;
}
#main-standings-container {
margin-top: 0;
margin-left: 3em;
width: 600px;
}
#main-left-container {
flex-direction: row;
max-width: 80%;
}
#main-latest-games-container {
width: 600px;
}</code></pre><p>I hide all the things I'm not interested in and reorganize the interesting bits for my needs.</p><h2 id="update-higlight-teams-in-standings-table">Update: Higlight teams in standings table</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/12/liigafi-demo.gif" class="kg-image" /></figure><p>After creating the custom styling and using the site with scores and standings next to each other for a day, I realized the layout is great for another improvement: highlighting teams in standings when hovering over their match.</p><p>I like this because it makes it easier to see where teams are in the standings and what kind of impact the match has and who's a favorite.</p><p>I created a Javascript script to do this:</p><pre><code class="language-javascript">let standingsNode = document
.getElementById("main-standings-container")
.querySelector("tbody");
function onHover(event) {
let children = event.target.children;
let teamsContainer;
if (children.length === 5) {
teamsContainer = children[3];
} else {
teamsContainer = children[1];
}
let homeTeam = teamsContainer.children[0].children[0].innerText;
let awayTeam = teamsContainer.children[2].children[1].innerText;
let teamNodes = standingsNode.querySelectorAll("tr");
teamNodes.forEach((node) => node.classList.remove("highlight"));
let selected = Object.values(teamNodes).filter((node) => {
let teamName = node.querySelector(".TeamNameTd").innerText;
return teamName === homeTeam || teamName === awayTeam;
});
selected.forEach((tr) => {
tr.classList.add("highlight");
});
}
let scoreContainers = Object.values(
document.querySelectorAll(".LatestGames_gameCardContainer__aEuDi")
);
scoreContainers.forEach((container) =>
container.addEventListener("mouseenter", onHover)
);
</code></pre><p>Whenever you hover over a match on the scores section, it'll get the names of the teams, filter the standings based on that and add a class to those selected.</p><p>I then added another piece of custom CSS to my Stylus definition above:</p><pre><code class="language-css">.highlight td {
background: black !important;
color: white !important;
}</code></pre><p>To invert black/white color scheme to highlight the teams and their records. Works like a charm!</p><h3 id="bookmarklet">Bookmarklet</h3><p>Currently I run it as a bookmarklet (requiring manual activation each time) but will probably make it into a Chrome extension later to make it automatically ran on page load.</p><pre><code class="language-javascript">javascript:(function() {let standingsNode=document.getElementById("main-standings-container").querySelector("tbody");function onHover(e){let t,n=e.target.children,r=(t=5===n.length?n[3]:n[1]).children[0].children[0].innerText,i=t.children[2].children[1].innerText,l=standingsNode.querySelectorAll("tr");l.forEach(e=>e.classList.remove("highlight")),Object.values(l).filter(e=>{let t=e.querySelector(".TeamNameTd").innerText;return t===r||t===i}).forEach(e=>{e.classList.add("highlight")})}let scoreContainers=Object.values(document.querySelectorAll(".LatestGames_gameCardContainer__aEuDi"));scoreContainers.forEach(e=>e.addEventListener("mouseenter",onHover));})()</code></pre><p></p>
Minimal Travel Table Top Game Collection 3: Project 108
2021-12-01T00:00:00Z
https://hamatti.org/posts/minimal-travel-table-top-game-collection-3-project-108/
<p>It's time for another <em>Minimal Travel Table Top Game Collection </em>project. If you missed the first two, you can find the <a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">first one</a> and <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/">second one</a> in my blog. All of these are created for my personal use only and I recommend supporting your local game stores and the creators of the original games by buying a copy of the ones you like.</p><p>First two sets were about taking existing games and creating smaller versions of them fitting into double-side printed standard cards. I created the first one out of a combination of curiosity and necessity and the second one because right when I got my first one, the world went into a lockdown and I had nobody to play with.</p><p>The motivation for this third one came last summer when I was visiting my friends and we played a few games during that trip. Games like <a href="https://boardgamegeek.com/boardgame/432/6-nimmt">6 Nimmt!</a> and <a href="https://boardgamegeek.com/boardgame/225482/texas-showdown">Texas Showdown</a> are wonderful small games that are nice to play in a pub or in a train or while waiting for the main thing to start. But carrying all those games separately takes quite a lot of space that I don't have in my backpack. An idea sparked in my head: maybe instead of picking up a collection of games that use limited amount of cards, I could combine multiple games per card.</p><h2 id="enter-the-rabbit-hole">Enter the rabbit hole</h2><p>When I mentioned this idea to my gaming group, I got into an adventure of board game history research that I wasn't expecting. It all started with the <a href="https://thewrongtools.wordpress.com/2019/10/10/the-everdeck/">Everdeck</a>. Everdeck was the first so called <em>Universal Card System</em> I had heard of. Through a well-crafted system of lots of tables and spreadsheets it brings the ability to play a wide variety of games with cards that are beautifully designed to not look like they are made for a dozen games.</p><p>Next, I learned about <a href="https://snepshark.itch.io/deck-of-many-dice">The Deck of Many Dice</a> that worked on a similar universal premise but was more focused on efficiency than hand-crafted design the Everdeck had. Both the Everdeck and The Deck of Many Dice had one wonderful thing in common: the development of both was well documented which provided me with a lot of food for thought but especially a lot of inspiration.</p><p>At this point, I knew there had to be more so I headed over to BoardGameGeek and found <a href="https://boardgamegeek.com/geeklist/252876/playing-card-game-systems">a great thread about Playing Card Game Systems</a> going through the history of them and introduced me to even more attempts by different people like <a href="https://www.singularity.games/p/deck.html">Singularity Deck</a>, <a href="https://www.decktet.com/">Decktet</a> and <a href="https://boardgamegeek.com/boardgame/153898/glyph">Glyph</a>.</p><h2 id="not-quite-so-universal">Not quite so universal</h2><p>I initially started from a small set of games I wanted to include, wandered in my research into the direction of universal systems and then decided to get back to a limited set of specific games I wanted to support: games that I like to play and know how to play.</p><p>The baseline for my set is <a href="https://boardgamegeek.com/boardgame/432/6-nimmt">6 Nimmt!</a> that uses cards numbered 1–104 and each number is associated with 1–7 bullheads for scoring. This gave me a good baseline of 104 cards to work with and start to see what other games could be built in.</p><p>Next, I added the aforementioned <a href="https://boardgamegeek.com/boardgame/225482/texas-showdown">Texas Showdown</a> that uses a subset of numbers between 0 and 74 and associates colors to these numbers based on which tens they belong to.</p><p>I then followed up with another trick-taking favorite of mine, <a href="https://boardgamegeek.com/boardgame/8129/sluff">7 Signum (also known as Sluff Off!)</a> that uses 75 cards (numbered 1–15 in 5 colors) for playing and another 27 for tokens.</p><p>Another set of games I really like playing are games with roles and social bluffing and deduction. Since I had plenty of cards in the set, I added <a href="https://boardgamegeek.com/boardgame/129622/love-letter">Love Letter</a>, <a href="https://boardgamegeek.com/boardgame/925/werewolf">Werewolf</a> and <a href="https://boardgamegeek.com/boardgame/131357/coup">Coup</a>.</p><p>Finally, I included <a href="https://boardgamegeek.com/boardgame/12942/no-thanks">No Thanks!</a> (uses just card numbers 3-35), <a href="https://boardgamegeek.com/boardgame/2051/pico">Pico</a> (uses cards 2–10, 13 and 16 with added red dots for scoring) and <a href="https://boardgamegeek.com/boardgame/172/sale">For Sale</a> (uses numbers 1–30 for properties and other cards for money cards with written down dollar amounts).</p><p>And since I was making semi-universal game cards, I added the traditional game cards (A, 2-10, J, Q, K in four suits) into the corner of the cards as well.</p><p>Because it has cards with numbers, it also plays a huge variety of other games, like <a href="https://boardgamegeek.com/geeklist/12110/games-playable-6-nimmt-deck-please-add">all the 22 found on this Geeklist</a> or <a href="https://boardgamegeek.com/geeklist/5498/games-sequentially-numbered-cards-aka-games-you-ca/page/3">these 54 in another Geeklist</a>.</p><h2 id="designing-the-cards">Designing the cards</h2><p>One underlying reason for many of these projects of mine is to become better at designing stuff. 2.5" x 3.5" card is such an interesting space for displaying information. I'm constanly researching how different games use that space to convey information and trying out my improving skills in that space.</p><h3 id="index">Index</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/p108-index.jpeg" class="kg-image" alt="A deck of white cards with varied colored stripes on the side." /></figure><p>First design decision that carried through to the end was that I wanted to make it easy to pick the correct cards for any given game. When I designed the first two collections, I made cards double-sided to fit in more games but that meant I constantly needed to flip cards around when I switched games and I didn't have any system to know what was on the back of which cards and it's a bit cumbersome.</p><p>So I designed an index system: on the left side of each card, there's a selection of colored stripes (with instrument icons for added accessibility) that match the games. I included a set of 3 index cards into the mix for reference. To be honest, the system is great but the design of the index cards especially is something I didn't spend enough time to be happy with and it's probably something I'll revisit in future editions.</p><p>I was playing around the idea of having QR codes included in index cards for each game and that QR code would lead into my website with rules written there but scrapped that idea in favor of simpler design. Since I'm the only owner of this set, I know where to find those rules anyway so I decided to cut that corner.</p><h3 id="the-anatomy-of-the-cards">The Anatomy of the Cards</h3><p>One aspect of card design that came up over and over again in my research was the symmetry and supporting different rotations and ways of holding the cards. If you look at a traditional playing card, it's readable in any direction and no matter how you put the card in your hand, on the upper left corner you see the suit and number.</p><p>I tried to keep this in mind but given the sheer amount of information needed to fit into the card, I had to cut that idea and only have each information once on the card.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/p108-card-anatomy-1.png" class="kg-image" alt="A picture of a card from the deck with different parts explained with arrows and text." /></figure><p>In the center in big bold font, I have the running number from 0 to 104. Below that, I used the space for games that require written information: Love Letter, Werewolf, Coup and For Sale.</p><p>On the top left corner, I have a circled number for Texas Showdown. The color of the circle denotes the color in the game, main number is for the card value and smaller number in parenthesis is the max number in that color.</p><p>On the right corner, I have 7 Signum with a colored circle and a number for cards for playing tricks and numberless circles for tokens. I also matched these colors and numbers with Coup and Love Letter: the colors match the original colors of roles in Coup and the numbers match the card value in Love Letter.</p><p>I use the left side for the colored index and the right side for Pico scoring (1, 2, 3 or 4 red dots).</p><p>Bottom left corner is for 6 Nimmt! scoring with bull heads and the bottom right corner is traditional playing cards upside down (to be in the traditional spot in the top left corner when rotated).</p><h3 id="the-likes-and-the-hates">The likes and the hates</h3><p>I worked on this project mainly in late June and then left it on hold for 3 months because I wasn't quite happy with everything. I didn't get a big revelation and I really wanted this deck to be printed and played with to learn more, so I made final adjustments in October and ordered the deck.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/p108-card-10.jpg" class="kg-image" alt="Card number 10" /></figure><p>Some cards I really like, especially the ones in the smaller numbers where they are used in a lot of games. Something like #10 is full of information which makes its design quite balanced. Every corner, both sides and the middle has something out there.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/p108-card-98.jpg" class="kg-image" alt="Card 98" /></figure><p>Other cards, like #98 feel a bit naked and less carefully designed because they only serve their purpose for a few games. I do hope though that it's just a thing when looking at them in isolation and doesn't bother as much when playing games.</p><p>I used multiple different fonts in these cards and I'm not 100% sure what I think about that yet. One reason for that was to make different games look a bit different so you'd get used to looking at the right numbers and would feel weird if accidentally looking at a different piece of information when playing.</p><p>The indexing system is good and it works and is generally my favorite part of this deck's design. It looks pretty damn cool when the deck is stacked and fanned just a tiny bit.</p><p>It took a month more to be delivered than promised and was marked as Completed on MakePlayingCards.com's website over a week before it arrived which was my first bad experience with them. I was pretty sure at one point that I'd never get my cards. I'm happy they arrived eventually.</p><p>There are some design flaws that ended up in this prototype because I was too shortsighted with my process. Even though I made a great indexing system, I still made the mistake of trying to convey that information through elements too.</p><p>The biggest miss is that I didn't add the numbers in the top left corner for cards that weren't included in Texas Showdown. It would not have hurt the Texas Showdown game play (because of the index) but it would have highly improved every other game that relies on those numbers.</p><p>Another oversight that I actually thought about but decided not to do and regret now was to not add four additional suits to the standard playing card decks. Many other universal decks have introduced other suits and there are a lot of games that could have been played with those extras so it was just my laziness not to include them. The empty space was there for me to take advantage of and I didn't.</p><h2 id="wrapping-it-up">Wrapping it up</h2><p>All in all, I'm really happy I got this project done. I can fit all these games I love into my backback wherever I go, meaning I can play more games more often. I learned a ton about universal card systems and card design that will be beneficial in the future even if all that new knowledge didn't contribute to this project quite yet.</p><p>As soon as I got the cards to my hand and was able to solo play a few different games and go back down the rabbit hole of universal systems, I already got a few new ideas for the future projects.</p><p>And that's the beauty of it. Building these prototypes and hobby projects for my own use is such a great way to learn and improve as a card designer.</p><p>I think my next project will be an evolution of this idea and leaning even heavier towards the universality. Meanwhile, I'm able to spend the Christmas holidays with family and friends playing games without having to bring in an extra luggage for just games.</p><h2 id="a-sidenote-2-player-pokemon-solomon-cubes">A sidenote: 2-player Pokemon Solomon Cubes</h2><p>Before we go, I wanted to quickly share another cool project that I built. I love Pokemon TCG and I like drafting and while the main game isn't very suited for drafting, cubes are custom-built collections of cards designed for drafting.</p><p>I recently built two very special style cubes, both meant for 2-player drafts, a bit more unique format, designed to be drafted in <a href="https://mtg.fandom.com/wiki/Solomon_Draft">Solomon Draft format</a>. <a href="https://www.youtube.com/watch?v=G3VS6Cz3FAM">One</a> is focused on the very early sets of Pokemon TCG and is designed by Andrew Mahone. The list for the <a href="https://cubekoga.net/Cube/443">other one</a> was sent to me by its designer TheGyroscopeEevee and it focuses on the SP era cards.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/solomon-cube-index-cards.png" class="kg-image" alt="Two playing card sized cards with list of card names in them" /></figure><p>Both of these are 80 cards + energies so I designated a UG Flip'n'Tray deck box for each and designed index cards above to have an easy-to-find list of cards included in the cubes when drafting.</p>
Hello code, my old friend
2021-11-24T00:00:00Z
https://hamatti.org/posts/hello-code-my-old-friend/
<p>It's been a while since I've been writing code more than a few lines at the time. Since the pandemic situation eased out and I've been able and allowed to organize in-person events again, the last month has been quite busy with events almost every night.</p><p>Every now and then I was thinking about spending a Saturday to code but then when Saturday came, I had trouble getting started and week after week, no code was being written. Given that I only code as a hobby these days, it wasn't really a problem but since I do like developing and building things, I really wanted to get back to it.</p><p>Last week I finally then had an evening with no events, no hockey games on TV and nothing else to do. I've been developing <a href="https://glc-checker.netlify.app/">a decklist validator</a> for an unofficial community format for Pokémon TCG called <a href="https://gymleaderchallenge.com/">Gym Leader Challenge</a>. As the community <a href="https://gymleaderchallenge.com/glc-format-update/">announced their first ban list additions</a>, I knew I had to do a bit of work to update the app.</p><h2 id="starting-point">Starting point</h2><p>When I originally built the validator in August and shared it with the community, it was built in the most prototype way ever: just make something that works and validate the use cases and needs with it. I had to test out a few things since I wasn't quite sure how implement them and ended up with two half-broken repositories of code. When a new set came out, I had to run some Node scripts on one project, then copy-paste the output into files on the other project and test and deploy from there.</p><p>It worked but only barely and every time a new set came out, I had to read through the code to remind myself how to run the updates. I had completely failed to document the whole process, since I was always in the mindset of "I'll fix this one day."</p><h2 id="improvements-and-fixes">Improvements and fixes</h2><p>Good thing was that I had something that worked. I was able to make the banlist function with the old code and deployed that which gave me plenty of time to tinker with the rewrite.</p><p>Since everything was a mess, I decided to do a full rewrite now that I had confirmed the need and usefulness of the application. And I also had learned from updating the content a few times what were the pain points on that end as well.</p><h3 id="unifying-two-projects">Unifying two projects</h3><p>First and foremost priority for me was to bring all of my experiments and pieces of code into one place. So I started a new project by creating three different sub-projects inside: one for <strong>admin tooling</strong> containing all the necessary scripts and documentation for how to do content updates; another one for the <strong>API </strong>that I use through Netlify's Functions as a serverless function; and finally one for the <strong>web frontend</strong> that the users interact with.</p><h3 id="documentation-documentation">Documentation, documentation</h3><p>For someone as passionate about documentation as I am, this project has been the most horrifying of my projects in terms of lack of documentation. This time I started with the documentation. I already knew how the application works and how I want it to be developed and operated so I started by writing a README file to both the root folder as well as the admin tooling folder. </p><p>In addition to writing things in readmes, I think one good way to document workflows is to add scripts into <code>package.json</code>. If everything you need to run is covered by those, it's easy to find out what's happening.</p><p>The documentation and naming is not yet perfect but it's a huge improvement over the older situation.</p><h3 id="interactive-cli-menus">Interactive CLI menus</h3><p>I'm a huge fan of command-line interfaces. They are not best for everything but for smaller scripts they are a nice balance between the power of command line and documentation through menus rather than having to remember flags and arguments.</p><p>To add new sets or to adjust the ban list (the two main data points for this app), I can run <code>npm run start</code> (probably will rename to <code>npm run cli</code> at some point) and get interactive menu that tells me my options and allows me to manage the data.</p><h3 id="data-separated-from-build">Data separated from build</h3><p>Another "fail-safe" I built into the system is that downloading new sets or adjusting the ban list don't actually change the data API uses but generates a local flat database (as JSON files) that can be then moved into the API with <code>npm run build</code>. </p><p>By building a separate build step, I can feel bit more confident that when developing or experimenting, I don't accidentally destroy the data for the API. </p><h2 id="improving-the-situation-for-future-me">Improving the situation for future me</h2><p>All of one evening's work and nothing to show for.</p><p>All this rewrite work didn't change anything (other than fixing some validation bugs) for the user. But it enabled a lot of possible improvements in the long run and that's why I really enjoy doing this kind of work. I've always been really excited about building tooling for the people behind the scenes. </p><p>Since I can now feel confident that when the next set of cards hit the market, I can update the data for the validator without a worry with just a few well-documented steps, I can focus all the attention to the improvements of the app.</p><p>I kinda want to start learnin Svelte and am considering building the frontend with that and then start envisioning new features and functionality.</p><h2 id="gym-leader-challenge-format">Gym Leader Challenge format</h2><p>A side note about the format itself. I think it's <strong>the best </strong>way to play Pokemon TCG these days. I <a href="https://hamatti.org/gaming/the-best-way-to-play-pokemon-tcg/">wrote about it</a> in August a bit more in depth in my gaming blog and now that I've been playing both GLC format and the current standard format quite a lot since, I'm really happy I found this format. It balances out so many annoying things about the fast-paced hard-hitting meta of standard.</p><p>So if you're a Pokémon TCG player who's growing a bit tired of the current format or want to explore something new for funzies, check out <a href="https://gymleaderchallenge.com/">gymleaderchallenge.com</a> and join our Discord community (find current link from the glc website) to find discussion, decklists and people to play with since it's not official format supported by PTCGO.</p>
Reaction GIF board with Stream Deck
2021-11-17T00:00:00Z
https://hamatti.org/posts/reaction-gif-board-with-stream-deck/
<p>Last weekend I was watching <a href="https://www.twitch.tv/lornajanetv">lornajane's Twitch stream</a> and we ended up having a discussion about the Elgato's Stream Deck in the stream. I've owned on for quite a while now, got it when I started doing streaming and then ended up using it much more for everything else than streaming.</p><p>One use case that I have for it, which is crucial in today's digital life is a reaction GIF / meme board.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/stream-deck-gifs.jpg" class="kg-image" alt="Stream Deck with two rows of gifs in button screens" /></figure><p>Stream Decks is uniquely well-suited for this task because each button is a small screen in itself. Which means that you can use the gif itself as the button icon so you don't accidentally send the wrong thing.</p><p>To create your own Stream Deck GIF reaction board, you need to add System > Text modules (we're just posting the link to the gif and Slack/Discord will show the animated gif in the UI).</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/Screenshot-2021-11-14-at-19.16.33.png" class="kg-image" alt="Stream Deck UI showing Text button with text set to URL for the gif and Please Enter after message checkbox selected" /></figure><p>Select "Press Enter after message" if you want the button to directly post the gif link or keep in unselected if you want to use these reaction gifs as part of other messages.</p><p>Set the Text attribute to the Giphy URL and upload downloaded gif as the icon and you're good to go.</p><p>Now you're ready for the 21st century worklife and digital communities. You can be the fastest to react with the best gif and your coworkers will be in awe when you always have the best response to everything – instantly.</p>
The most social and loneliest job: being a solo developer advocate
2021-11-10T00:00:00Z
https://hamatti.org/posts/the-most-social-and-loneliest-job/
<p>At the beginning of the week I gave a talk in <a href="https://2021.devrel.net/">this year's DevRelCon</a> about my experience as the only developer advocate of our company and how it creates an interesting juxtaposition of social work and occasional loneliness.</p><h2 id="the-social">The Social</h2><p>I'm in a very lucky position that I get to do something I truly love as a job. As a developer advocate at <a href="https://futurice.com/">Futurice</a>, I get to work and interact with hundreds of people every month and help developers learn new stuff, to meet each other and to have a good time.</p><p>As a developer advocate, I work in the middle of the company and the community, representing the company to the community and vice versa.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/who-i-work-with-1.png" class="kg-image" /></figure><p>On the company side, I work with a lot of different groups: for our developers, I organize opportunities to learn, to share, to meet each other and to have a good time while doing it. I run <a href="https://futurice.com/techweeklies">Tech Weeklies</a>, organize our annual hackathons and a lot more. I work closely with our marketing and recruitment to help provide the developer point of view for our talent outreach initiatives. For example, I run our <a href="https://futurice.com/devbreakfast">developer newsletter Dev Breakfast</a> with our brilliant marketing team. With Human Care (that's what we call HR) and sales, I try to do my part in helping developers have a good place to work at.</p><p>On the community side, I get to work with brilliant and kind developers across different communities, technologies and cities. A big part of my work happens through local events: hosting, sponsoring, speaking in and participating and through those events, I get to meet a lot of people. I also try to do my best to help the local community organizers provide events and talks to their communities.</p><p>And one very rewarding part of my job is to lead our student and university collaboration where we get to host events with students: sharing information, hosting workshops, helping students learn new skills and to giving them opportunities to connect with developers in the industry.</p><p>All that means two things: one, I have a job I really really enjoy doing. Second, it's a very social job (which occasionally means I'm pretty worn out after the week) with a lot of discussions with different people.</p><h2 id="the-loneliness">The Loneliness</h2><p>Yet somehow, I've managed to find bubbles of loneliness inside it. It took me a while to realize that it was loneliness that I felt and even longer to understand how it was even possible.</p><p>But if I look at what I do with all these groups, it becomes bit more clear:</p><p>With <strong>the developers</strong> on both sides, we talk about technology. Sometimes it's about getting excited about details of a language or library, sometimes about the big picture and the impact. And as a developer myself, I love it. But we don't talk about developer advocacy.</p><p>With <strong>the marketing and recruitment </strong>colleagues, we talk about the impact my work has on the business and bringing in new developers into the company. We talk about the details of running a newsletter and how to improve it and so on, but we don't talk about developer advocacy.</p><p>With <strong>the human care and sales </strong>people, we talk about how to make the company a great place for developers to work and how to find the best match between projects and developers.</p><p>With <strong>the community organizers, </strong>we talk about how we can support their actions and help them find speakers from the community and spaces for their events. But we don't usually talk about developer advocacy.</p><p>Finally, with <strong>the students and university </strong>people, we help them learn new skills and host workshops and help them connect with people in the industry. But we don't talk about the developer advocacy.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/11/the-loneliest.png" class="kg-image" /></figure><p>That leaves me in the center, all alone.</p><p>The lack of having teammates who are equally excited about developer advocacy itself, leads to feelings of loneliness. When you always think and talk about the impact and the big picture and the effect your job has on other people and teams but never get to get truly excited about the job itself, it can be bit demoralizing sometimes.</p><p>And that's why I think this job can be the most social and the loneliest at the same time.</p><h2 id="the-support">The Support</h2><p>If you're in my situation, being solo developer advocate in a company and you're feeling the lack of peers to chat with, I highly recommend getting involved with devrel communities.</p><p>I noticed this entire thing when some friends of mine, who are developer advocates, visited Helsinki and we'd have long lunches and dinners – I once even had a brilliant excited discussion about developer advocacy in a job interview few years ago that felt awesome.</p><p>Having these people to chat with outside the job won't solve everything but it can help out from the pinch.</p><p>A few groups I mentioned in my talk by name are <a href="https://devrelcollective.fun/">DevRel Collective</a>, an online community of a couple of thousand devrel professionals who get together to share the good and the bad to be a great community of people to chat with, and <a href="https://www.writethedocs.org/">Write the Docs</a> community for people who work with documentation and writing to people. In addition to running meetups and conferences, they also have a wonderful <a href="https://www.writethedocs.org/slack/">Slack community</a>.</p>
Tiny handy tools: Community Edition
2021-10-27T00:00:00Z
https://hamatti.org/posts/tiny-handy-tools-community-edition/
<p>Last week I <a href="https://hamatti.org/posts/tiny-handy-tools/">wrote about the tiny handy tools</a> that I use and that make my life much easier. After that, I asked developers from my communities what similar tools do they use and here are some picks from those replies.</p><h2 id="yoink"><a href="https://eternalstorms.at/yoink/">Yoink</a></h2><p>Yoink is a drag-and-drop "tray" that allows you to move objects: files, images, text etc by dragging it out from one application to Yoink, navigating to the new place and dropping it to the new one from Yoink. I have a feeling this is gonna become one of my favorites.</p><h2 id="numi"><a href="https://numi.app/">Numi</a></h2><p>Numi was introduced to me as "I always replace native calculator with this on Mac". It's an calculator app that does a whole lot more than just calculations: it handles a lot of conversions (timezones, units, currency) and supports things like variables for more complex calculations.</p><h2 id="bartender"><a href="https://www.macbartender.com/">Bartender</a></h2><p>In my earlier post, I mentioned I use Dozer to manage my menu bar and was recommended Bartender that does what Dozer does but a bit more and gives you more flexibility and power to control how your menu bar works.</p><h2 id="next-meeting"><a href="https://www.macmenubar.com/next-meeting/">Next Meeting</a></h2><p>In this era of video calls and meetings, Next Meeting is a handy tiny tool that integrates into the menu bar, tells you when your next meeting is and even allows you to join the call right from the menu bar.</p><h2 id="meeter"><a href="https://trymeeter.com/">Meeter</a></h2><p>Another meeting related helper tool is Meeter that does similar things to Next Meeting. It lets you keep track of your meetings for the day and connect to those video meetings directly from the tool.</p><h2 id="stats"><a href="https://github.com/exelban/stats">Stats</a></h2><p>Stats is another menu bar tool that lets you view different statistics about your computer: CPU and GPU utilization, battery level, network usage and so on.</p><h2 id="istat-menus"><a href="https://bjango.com/mac/istatmenus/">iStat Menus</a></h2><p>Another stats tool is iStat Menus. I don't have a lot to say about these two because I pretty much only ever care about battery level myself but for those who want to see the other stats, these two seemed to be favoured.</p><h2 id="rectangle"><a href="https://rectangleapp.com/">Rectangle</a></h2><p>I introduced Magnet as my window layout manager and learned about an open source competitor to it called Rectangle. It does what a good window moving tool does: allows you to move and resize windows with handy keyboard shortcuts. Everyone should have one of these kind of tools.</p><h2 id="shush"><a href="https://apps.apple.com/us/app/shush-microphone-manager/id496437906">Shush</a></h2><p>Shush is a microphone manager that adds push-to-talk hotkey to Mac so you don't accidentally have your mic open when not talking in a meeting. And for those who prefer the opposite, it also provides push-to-silence feature that mutes your mic when a key is pressed.</p><h2 id="paste"><a href="https://pasteapp.io/">Paste</a></h2><p>Paste is a clipboard manager that provides unlimited clipboard capacity, search functionality and iCloud sync to make your clipboard available across all your i-devices.</p><h2 id="theine"><a href="https://www.ixeau.com/theine/">Theine</a></h2><p>There are a couple of tools that allow your Mac to stay awake. I didn't list it in the original one but I use <a href="https://intelliscapesolutions.com/apps/caffeine">Caffeine</a> and for this post, I was recommended Theine that offers a bit more functionality and options like setting a timer.</p><h2 id="cleanshot-x"><a href="https://cleanshot.com/">CleanShot X</a></h2><p>CleanShot is a screen capture tool for screenshots and recordings that does much more than Mac's built-in tool that I've been using all these years.</p><h2 id="albert"><a href="https://albertlauncher.github.io/">Albert</a></h2><p>I introduced Alfred as my favorite launcher tool and learned that there's also Albert for Linux users.</p><h2 id="hammerspoon"><a href="https://www.hammerspoon.org/">Hammerspoon</a></h2><p>Automate all the things! Hammerspoon is an engine that connects Lua scripting language to macos operating system allowing you to build automations and extensions for anything you want to achieve with your macos.</p><h2 id="the-fuck"><a href="https://github.com/nvbn/thefuck">The Fuck</a></h2><p>This command-line tool is kinda like an autocorrect for your shell commands. If you run a command that is incorrect, running the command <code>fuck</code> attempts to guess what you wanted to say and prompts you to accept or cancel the new command.</p><h2 id="f-lux"><a href="https://justgetflux.com/">f.lux</a></h2><p>f.lux is an automator that gradually reduces the blue tones of your screen towards a more yellow tones as the day turns into the evening. You won't notice the change as you use the computer but they say it's better to avoid blue screens towards the night.</p><h2 id="karabiner-elements"><a href="https://karabiner-elements.pqrs.org/">Karabiner-Elements</a></h2><p>Karabiner-Elements provides a way to configure key mappings.</p>
Tiny handy tools
2021-10-20T00:00:00Z
https://hamatti.org/posts/tiny-handy-tools/
<p>Since I haven't been able to write a proper <a href="https://uses.tech/">/uses page </a>for my site, I'll share some small tools that I really enjoy using for work and pleasure with my Macbook setup.</p><p>I won't be talking about my main tools like code editors, terminals, browsers or note taking apps in this one but rather tiny handy tools that make life a bit better.</p><h2 id="dozer"><a href="https://github.com/Mortennn/Dozer">Dozer</a></h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/10/dozer-demo.gif" class="kg-image" /><figcaption><a href="https://github.com/Mortennn/Dozer">Demo from project page</a></figcaption></figure><p>The first tool that is an absolute lifesaver for me is <a href="https://github.com/Mortennn/Dozer">Dozer</a>. If you are a Macbook user who has a lot of tools in the menu bar and use the laptop predominantly without an external screen, it's easy to run out of space since the menu bar icons share the space with the menu bar of applications.</p><p>Dozer adds two circles into the toolbar: you drag one of them where you want to add separation and clicking the other one will hide everything left from the circle. </p><p>This allows me to keep most of the icons hidden and menu bar nice and clean but still access them whenever I need.</p><h2 id="alfred"><a href="https://www.alfredapp.com/">Alfred</a></h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/alfred-timezone.png" class="kg-image" alt="Alfred screenshot showing current time in San Francisco, New York, London, Helsinki and Sydney" /></figure><p>Macos's default Spotlight is a nice tool to open applications but it's rather limited in its usefulness. Alfred is kind of a drop-in placement that enables the same kind of interface but pretty much unlimited customization opportunities.</p><p>For example, in the above example I can define timezones I want access to and with a simple command I see the info right in the Alfred view. You can also build more complex commands and pipelines and automate a lot of of things on your life.</p><h2 id="itsycal"><a href="https://www.mowglii.com/itsycal/">Itsycal</a></h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/itsycal.png" class="kg-image" alt="A calendar pop up with current date highlighted" /></figure><p>Itsycal is a small popup calendar that lives in the menu bar and provides a simple view into the calendar. It supports connecting it to Mac's Calendar to see events but I just use it to check what date is which day of the week and checking the week numbers. It's so handy when booking things or discussing what's happening when, for example when on a video call with someone.</p><h2 id="hand-mirror"><a href="https://handmirror.app/">Hand Mirror</a></h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/handmirror-demo.png" class="kg-image" alt="A webcam view pop up on the desktop" /></figure><p>Another menu bar app is Hand Mirror that does what the name says. In the world where we are constantly on video calls, it's nice to be able to check what the camera sees before joining a call.</p><p>I've set it up so that pressing <code>ctrl+1</code> opens up my main camera view in a small pop up window.</p><p>It only takes a few seconds to double check that everything's good and okay. I used to use Photo Booth (with Alfred + "pb" as search query) and it worked okay but I like that Hand Mirror is very fast and doesn't open any new apps.</p><h2 id="magnet"><a href="https://apps.apple.com/us/app/magnet/id441258766">Magnet</a></h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/magnet.png" class="kg-image" alt="An application menu showing different options for moving a window" /></figure><p>When I'm not using my laptop in laptop mode but with a separate display, I like to be able to move windows around effortlessly. </p><p>My most common use case is that I have browser/Slack/Discord/[whatever main app] being 2/3 of the screen on the left and iTerm/PDF reader/auxiliary app as 1/3 on the right. However sometimes I want to do a 50/50 split with two windows, maximize one window or some other layout.</p><p>Magnet is a wonderful tool for that. After having used it so much, everything is in the muscle memory that I can move windows around without thinking about the shortcuts.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/chrome-shortcuts.png" class="kg-image" /></figure><p>As an added bonus, using Macos's own <em>Keyboard -> Shortcuts -> App Shortcuts </em>settings, you can define custom shortcuts that run menu items. I've set it up so that <code>ctrl+option+'</code> moves current tab into a new window.</p><p>That combined with Magnet shortcuts, I can easily separate an active tab (for example, instructions for something) to its own window and throw it to the 1/3 on the right to have access to both windows on one glance.</p><p>(PS. Keyboard shortcuts are wonderful)</p><h2 id="reflector"><a href="https://www.airsquirrels.com/reflector">Reflector</a></h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/reflector.png" class="kg-image" alt="iPad screen mirrored into Macbook with Reflector" /></figure><p>Ever since I got my iPad, I wanted an easy way to share the screen of my notes or sketches or anything really on the iPad to video calls and livestreams. Reflector 4 was the answer to my needs.</p><p>It's a really easy to set up and use: just have it open on your Mac and then choose screen mirroring from the iPad and boom, you're good to go. </p><h2 id="modern-unix-list-"><a href="https://github.com/ibraheemdev/modern-unix">Modern Unix (list)</a></h2><p>Modern Unix is a GitHub repository of shell tools that are more modern approaches to default tools like <code>cat</code>, <code>ls</code> and <code>grep</code> that can often be used as a drop-in replacement.</p><p>I use many of the tools listed and they have really improved my command line workflow as I <a href="https://hamatti.org/posts/why-i-love-command-line/">prefer to do a lot of the things on my Mac with the command line</a> rather than GUI apps.</p><h2 id="what-other-people-are-using">What other people are using?</h2><p>After posting this blog post, I shared it with my communities and got a lot of recommendations for tools. So many of them that I decided to write a follow-up post: <a href="https://hamatti.org/posts/tiny-handy-tools-community-edition/">Tiny handy tools: Community Edition</a>.</p>
Over a year of weekly blogging
2021-10-13T00:00:00Z
https://hamatti.org/posts/over-a-year-of-weekly-blogging/
<p>My blog post on <a href="https://hamatti.org/posts/python-3-10-is-out-and-im-excited/">Python 3.10 on October 6th</a> marked a full year of weekly blogging on this blog. It was a huge personal milestone for me and this blog post is the retrospective of this experiment.</p><p>I've written quite a bit about blogging during this experiment. I wrote about <a href="https://hamatti.org/posts/you-should-start-a-blog-today/">why I think blogging is beneficial to everyone</a>, <a href="https://hamatti.org/posts/i-gamified-my-own-blog/">gamifying my own blog with achievements</a>, <a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed/">the importance of providing an RSS feed</a> and <a href="https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/">how blogging has become a great refactoring tool for myself</a>. I may repeat some bits from these but won't go in-depth into those topics here. I recommend reading them through as well if you're interested in blogging as a developer.</p><p>At the end of this post, I also share posts and articles I've found insightful from other people so you can get different ideas and perspectives from different people.</p><h2 id="the-experiment-and-hypotheses">The experiment and hypotheses</h2><p>Every good experiment needs some hypothesis that it's testing so we can learn from them. Some learnings come unexpected regardless of our hypotheses but if we have none, we risk not finding out things.</p><h3 id="experiment-duration">Experiment duration</h3><p>I set a goal of blogging weekly for 1 year, or 52 consecutive weeks. Now that I've reached it, doesn't mean I'm gonna stop blogging or even reducing my pace but I wanted to set an ambitious goal that was long enough to make the experiment impactful to learn from.</p><p>Giving an experiment an end date also helps me mentally: it allows me to approach it as a project that I can finish one day and not a never-ending thing. I was always merciful enough to myself that if I felt like I start hating writing, I'd stop anyway but for me, these kind of hard goals really help me push over the minor inconveniences.</p><h3 id="hypothesis-1-writing-regularly-helps-to-write-regularly">Hypothesis #1: Writing regularly helps to write regularly</h3><p>My first hypothesis for this experiment was something I already knew to be true from my previous experience. In 2014, I published over 70 blog posts and articles (+ a lot of unpublished stuff in my notebook) in a year.</p><p>From that experience, I had learned from that writing on a constistent pace makes writing easier. I wanted to reassess that experience with this experiment. If you've ever created something (writing, painting, drawing, etc), you might have encountered how difficult it is to get started when all you have is an empty page/canvas. That's how I often feel too.</p><p>As I kept writing and publishing weekly, I started to notice two things:</p><ol><li>It's easier to write when I write consistently. I found that weekly rhythm is something that works well for me. It keeps me in a writing-thinking mode all the time and when I open the editor, I have something to say but it doesn't consume all of my time and energy like writing daily would.</li><li>I come up with ideas for what to write much easier. This used to be a huge challenge for me. "<em>I don't have anything to write about"</em> turned out to be just a struggle with an empty canvas. As a blogger, I started to see things through the lense of writing so I encountered a lot of things and immediately thought, <em>"This would make a great blog post". </em>A bit more about this later.</li></ol><p>So hypothesis #1 was absolutely confirmed. A routine and momentum are so crucial for getting many things in this life done.</p><h3 id="hypothesis-2-consistency-is-king">Hypothesis #2: Consistency is king</h3><p>A bit less important hypothesis that I wanted to test out was about gaining readers and following on social media. I'm not great at social media, I barely have any following and sometimes when working in developer relations, I feel like I should be a bit better at it so I could have an larger positive impact with the work I do.</p><p>October 2020, I had 1414 Twitter followers. Now at the time of writing this in October 2021, after a year of weekly blogging and somewhat active participation in Twitter, I have 1582. I gained roughly a hundred followers in a year.</p><p>There are many reasons probably for this but I did kind of expect the regular blogging to have a bit more of an effect in the growth. I could analyze the reasons deeper but social media clout isn't really the motivation for me writing so I don't care to do that here.</p><p>I don't have detailed analytics for my website visitors. I only use Netlify's built-in server log analytics that gives me the last 30 days and I usually use that to check if a conference / meetup talk or something that went "micro-viral" in social media had any real effect – so far it hasn't. In general, I don't have a lot of readers or visitors to my website or blog. Way less than 10k monthly visitors.</p><p>A good side in having a smaller audience is that I know a huge part of my audience personally. They are friends, colleagues and people from the different communities I do things with. And it also means I get to experience the new ideas, inspiration or growth they gain by learning from my writing.</p><h2 id="how-did-the-experiment-go">How did the experiment go?</h2><p>Here are things I did, learned or experienced during the past year. I have not analysed them enough to confirm any kind of causation so take these as anecdotal experiences and not as a silver bullet to any situation.</p><h3 id="planning-in-advance-helps">Planning in advance helps</h3><p>One question I get a lot is how hard is it to come up with ideas for writing. And I wouldn't say it's easy per se but I have some (very light) "processes" in place that helps with it. I already mentioned earlier that the momentum helps a lot and I come up with a lot of ideas because I'm constantly writing.</p><p>One interesting aspect that's completely hidden from the readers is that I don't publish the blog posts in the same order as I write them. There are multiple reasons for why this is: I have some recurring series (like <a href="https://hamatti.org/posts/learning-rust-pattern-matching">Learning Rust</a> on the first week of the month), the blog post is topical for a future date (like <a href="https://hamatti.org/posts/python-3-10-is-out-and-im-excited">Python 3.10</a> on the Wednesday after the release) or because something more urgent came up after I had written something.</p><p>A really good benefit of this is that if I have some time to write something lighter (mostly meaning less research demanding) when I'm waiting for the train, I can write those whenever and they provide me a lot of buffer for inspiration or for posts that require multiple weeks worth of research.</p><p>If I'd always publish a post when it's done, I would end up in a situation where I always have max one week of prepare time and there would be zero buffer ever for bad weeks.</p><p>Next thing that helps a lot is something I started around mid-way through when I realized that I'm able to publish every week. I wrote all the week numbers of 2021 into a notebook page (I use Notion for my digital notebooking), filled in the previous posts I had already published and started to plan using it. I often have most of the weeks 2-3 months ahead planned on a topic level.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/10/blog-planner.png" class="kg-image" alt="A list of blog topics separated by months and week numbers" /></figure><p>For example now, there's only one empty slot for the rest of the year. It's not set to stone though: I often move things around, scrap ideas and bring in new ones but it does give me a peace of mind when I know I have something coming up.</p><p>Visualizing it also helps me plan for the balance of topics. Since my aimed audience is mainly developers, I aim to have at least 50% of my blog posts technical in nature and of the others, many are related to being a developer even if not directly technical. I use color coding for this: orange background for technical, blue for personal, pink for community and green for other stuff.</p><p>This balance also helps because technical blog posts almost always take more time: researching, learning more, writing examples, testing and so on is more time consuming than writing about my lived experience.</p><p>In addition to my weekly list which is mostly for blog posts that I have decided to write and fleshed out a bit, I have a different page on my notebook for ideas. Right now, I have roughly 40-50 ideas that I have thought about but haven't decided to write yet. </p><p>On any given day, I might think about 2-3 topics that I've planned so even when I'm not writing or taking notes, in the back of my head, I process ideas related to what's on my plans.</p><h3 id="monthly-series-bring-stability-and-interest">Monthly series bring stability and interest</h3><p>A good segway from planning is towards my monthly series. Since January, I've dedicated the first week of the month for my <a href="https://hamatti.org/posts/learning-rust-pattern-matching">Learning Rust</a> series. I didn't plan it to be like that but the first few ones ended up being at the beginning of the month so I retrofitted the entire series to be like that.</p><p>It's helped me because I can fill in the blog planner from the previous section: for each month, first week of the month is <em>"Learning Rust #N: ???" </em>until I come up with a topic. It's not set to stone though. For example this month I didn't have a Learning Rust post because I hadn't had time to code anything in Rust recently.</p><p>Another reason for having a regular series on a topic is that it can help build habits for your readers. Over time, as people learn every first Wednesday is Learning Rust Wednesday, they are more likely to remember to check it out and read it. Maybe they even start to wait for it.</p><p>I learned this trick from my meetup organizing: since the beginning, we've focused on having our Turku <3 Frontend meetup on the last Wednesday of the month because it helps people plan even before we can set up event marketing and in its own way, does big part of the marketing for us as people can put it into their calendars early on.</p><p>Another series that has been much less consistent is my <a href="https://hamatti.org/posts/codebase-ep-4-web-components-with-matias">recaps of codebase shows</a>. When I had those streams, I would write a follow-up / recap of it for few weeks later to provide an extra information piece for the show itself. That way I had the livestream once a month + a blog post about it once a month and being on audience's radar every two weeks.</p><h3 id="weekly-schedule-is-tough">Weekly schedule is tough</h3><p>Regardless of all these helping functions I've built over time, it's still tough to write and publish every week, for multiple reasons. It's been even more tough during the pandemic because for years, my habit was to go to a local pub to write. That was my creative zone and being stuck at home made it super difficult to get into the creative mood.</p><p>And then there's work and life and all the other things that take time and focus away. I'm a single guy so I don't have a family to take care of, which increases my available time a lot compared to some other people who have families. The lesson there is not to compare your output on anything with someone else who lives a different life from yours.</p><p>For some people, setting up a dedicated time for writing is what works for them. I don't do that because I'm not very good at that. I'm much more inspiration-driven writer so I write when I feel like writing and that's also why I need so many extra things like planners and idea banks to make sure I have material for writing all the time. If you want to try regular blogging, try out what works for you.</p><p>When I lived in San Francisco in 2014 and I was writing a blog about my experience there, I always wrote for an hour before work in the morning. That worked back then but right now, it doesn't fit my life so I've taken a different approach.</p><p>Sometimes this inspiration-driven approach means it's Tuesday night and I have nothing done and then I just push through, pick an idea from my list and write about it.</p><p>A great benefit of my way is that when I do feel inspired, I may write 3-4 blog posts on one sitting and that provides me enough buffer to meet my weekly quota even when some weeks I struggle with inspiration, time available or other reasons.</p><p>It was around week 30 or 35 when I felt comfortable enough to add "New blog post every Wednesday!" text to my blog's index page.</p><h3 id="sidenote-building-a-buffer">Sidenote: Building a buffer</h3><p>This blog post is mostly about what I experienced but I do want to make one sidenote as a piece of advice for new bloggers.</p><p>You've probably seen a lot of blogs that have one (usually about the setup for their blog and what they plan to write about) or two blog posts and then it's abandoned. That happens quite easily and in all honestly, it's not a bad thing. If you feel like blogging wasn't for you, it's okay to stop.</p><p>But if you want to write more but struggle a bit to maintain a consistent pace in the beginning, I recommend writing things in a buffer and then publishing them on an interval (weekly, bi-weekly, monthly, whatever works for you). That way you can still publish things to keep the blog going even when you feel like not writing.</p><p>For a lot of people, seeing and hearing people read your blog is a big motivator but it takes time for people to find your blog and often when they read one post, they check what else is there. I'd even advice writing 2-3 blog posts before you publish your blog and then adding them all there (maybe even back-dating them) so people can read multiple pieces when they find you.</p><h3 id="my-blog-is-my-platform">My blog is my platform</h3><p>One discussion I had with myself a lot (and have had for years) is what I should blog about. This is especially in relation to the fact that having a tight focus and niche topic makes it easier to build readership as your readers will most likely find every post relevant and interesting. With tight focus, you'll also have an easier time in marketing your blog because there's a well-defined audience.</p><p>However, I write my blog mostly as an outlet for ideas that live in my head. I write because I love writing and because it helps me make more sense of those ideas. A very helpful blog post I read was Rach Smith's <a href="https://rachsmith.com/to-be-whole-is-the-goal/">To be whole is the goal</a> (btw, love the title of it) that gave me the required confidence that I don't need to build an audience with any means necessary.</p><p>One of the greatest parts of having your own website and your own blog that you control, is that nobody's gonna come and tell you (or atleast they don't have any authority to affect it) what you can write about. If I'd publish my posts on someone else's platform, they might only be interested in my Javascript posts and not my posts about fun board games.</p><h3 id="building-a-body-of-work">Building a body of work</h3><p>I've talked about writing and public speaking a lot with my buddy James over the years we've known each other and one huge idea that I learned from James is the concept of building a <em>body of work.</em></p><p>The more I write, the more posts I have in my blog. And even if they don't catch an audience immediately when I publish them, I can use all of those in the future – and I do that a lot.</p><p>If I'm mentoring a junior developer and they ask me about a topic that I have written about, I can send them a link as extra material for the discussion. If I'm applying to speak in a conference, I can accompany that proposal with links to relevant blog posts and talks. If I apply for a job, I can do the same.</p><p>Month after month, my most read blog post is <a href="https://hamatti.org/posts/how-to-scrape-website-with-python-beautifulsoup/">How to scrape a website with BeautifulSoup from May 2020</a>. I don't share it all the time but people find it and read it way more than any other posts I have. And that's 1.5 years after I wrote it. I refer to that blog post every time I forget how to do some part of it.</p><p>Very importantly, if I forget how to do something that I've documented in my blog, I can check it.</p><p>And the only thing that makes it possible to do that, is to build the body of work over time. It takes a long time to write and publish, say 200 posts. That's why starting now is the best time to start.</p><h3 id="where-do-my-ideas-come-from">Where do my ideas come from?</h3><p>I've talked about how it becomes easier for me to come up with ideas for these blog posts the more I blog but where do those ideas then come from?</p><p>I teach programming and mentor students and junior developers a lot. Both locally in in-person workshops like <a href="https://medium.com/the-codelog/coaching-at-codebar-has-given-me-a-lot-81b58664492">codebar</a> or via student organizations and online on discussion forums and developer communities. Pretty much regardless of the programming languages people are learning, I see the same questions pop up over and over again. So I've started to write those down into the blog so I can refer to them and also so I can check that I'm not forgetting to explain something.</p><p>I even wrote a blog post for guidance for people on <a href="https://hamatti.org/posts/how-to-ask-help-for-technical-problems/">how to ask great technical questions</a>.</p><p>Another source is being active in developer communities at work and at my hobbies. <a href="https://futurice.com/">My colleagues at Futurice</a>, <a href="https://koodiklinikka.fi/">Finnish-speaking developer community Koodiklinikka</a> and <a href="http://turkufrontend.fi/">our local frontend community Turku <3 Frontend</a> are just some groups I hang out at and get to talk shop with awesome developers day in and day out. A lot of great topics spark from those. I also run a newsletter called <a href="https://hello.futurice.com/dev-breakfast">Dev Breakfast</a> at work and interacting with developers through that is a great source for seeing what's happening in the developer world.</p><p>I also write about projects that I've built and small things I've learned. I helped a colleague <a href="https://hamatti.org/posts/validating-dynamic-data-conditionally-with-joi/">debug a problem with Joi</a> and wrote down what I learned. I participated in <a href="https://hamatti.org/posts/advent-of-code-2-borrows-unpacks-and-lots-of-compiler-errors/">Advent of Code and wrote a weekly journal</a>. I open-sourced <a href="https://hamatti.org/posts/project-card-print-css/">a CSS mini-library for printing game card prototypes</a> so I wrote about that. And so on and so on. You get the picture.</p><p>I speak regularly in meetups, conferences and other events and those talks translate nicely to blog posts. I usually write the blog posts after I'm "done" with the talk (might be 3-6 months or even 1+ years) as the ideas have marinated and been revised based on comments.</p><p>Finally, a huge source of topics is my mind. I have a lot of opinions on a lot of things and then I tend to write about them if they feel appropriate for a blog.</p><p>Even before I did this weekly blogging thing, I encountered these same things on a similar regularity but I didn't spot them as potential blog post topics in a same way as I do now.</p><h3 id="blogging-driven-development">Blogging-driven development</h3><p>One really cool benefit that I've seen from keeping myself accountable for an audience is that it drives me to do more. Learning Rust series has definitely encouraged me to keep learning Rust and keep building things so that I have something to write about. Without that series of posts and my desire to continue the series every month, I probably would have written less Rust.</p><p>While my motivation for learning Rust is not so that I can write blog posts, writing those posts helps me stay on the topic when I would otherwise wander to other things.</p><h3 id="the-great-posts-are-born-from-momentum">The Great Posts are born from momentum</h3><p>Are all my posts great masterpieces? Definitely not. The internal pressure that the weekly schedule creates is a great way to silence the inner critic.</p><p>The incredible side effect of this is worth some blog posts being less magnificient. The momentum helps me write the good ones. If I ever want, I can just one day hide all the ones I don't like that much and keep a collection of the good ones. But I don't really have a need for that either.</p><p>One thing I know for sure: without writing the less great ones and keeping up with my weekly schedule, I wouldn't have had the momentum and writing mood to get the good ones done. I would have ran into the struggle of the empty page and never written anything.</p><h3 id="documenting-for-myself">Documenting for myself</h3><p>I <a href="https://twitter.com/Hamatti/status/1437471453205643268">recently tweeted</a>: </p><blockquote>Tech blogging is often framed in a "write to teach others" which often leads to questions like "am I good enough to write about this topic". I like to frame it as "write about things you've learned". Learning it, then doing bit of research to write a post helps you learn more.</blockquote><p>For example, Learning Rust series is not an educational series. It's a journal for me to document my experience of learning a new language. Had I thought I need to be a Rust expert to write about Rust, I would have never done this much about Rust and I hope my posts have inspired others to pick up Rust as well.</p><p>It's great to write to help others learn. But it also puts a burden on many people, who as a result won't blog at all because they feel that they are not good enough.</p><p><a href="https://hamatti.org/posts/how-to-scrape-website-with-python-beautifulsoup/">How to scrape a website with Python & BeautifulSoup</a> didn't start as a "let me teach you how to use this library" but as a "I always have to check these things from docs so it's easier to find them from my blog written by me". And I've used that blog post a lot and it's been super helpful for me. Another example of this is my post on <a href="https://hamatti.org/posts/how-to-parse-command-line-arguments-in-python/">parsing command line arguments in Python</a>. Same motive, same approach and both seem to have been helpful for many others as well.</p><h3 id="what-kind-of-content-do-i-write-about">What kind of content do I write about?</h3><p>I'm a software developer and community builder so most of my interests are within that context. I did some math on the blog posts I've published between October 18th 2020 and October 6th 2021. I was surprised that 65% of them were directly technical (and many others less technical but related to being a developer). I always felt that my blog wasn't <em>"that technical" </em>but I stand corrected.</p><p>I categorized all the posts loosely to different categories (many overlap with each other so I just put them into some)</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Category</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>Technical posts</td>
<td>34</td>
</tr>
<tr>
<td>Personal</td>
<td>8</td>
</tr>
<tr>
<td>Blogging</td>
<td>4</td>
</tr>
<tr>
<td>Worklife</td>
<td>3</td>
</tr>
<tr>
<td>Board Gaming</td>
<td>2</td>
</tr>
<tr>
<td>Community</td>
<td>1</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>52</strong></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><h3 id="love-getting-positive-feedback">Love getting positive feedback</h3><p>I can let you in on a secret: people like positive feedback and saying thanks. I'm not an exception to that (even though I'm not very good at taking positive feedback). When someone sends an email or a tweet saying "Hey, I read this blog post of yours and it was good/entertaining/helpful", it feels awesome.</p><p>I've started to do that more with blog posts that I read but even then I don't do it enough. Social media is conditioning us into staring likes and retweets and follower numbers as the source of truth but what those miss is the times when people read your blog posts and enjoy them. I know from my own experience that I don't always like/retweet everything I see in social media even if I enjoy them. So I try to remind myself that others can enjoy my writing too even if it doesn't show up in those metrics.</p><p>So if you've read something this week that was interesting, captivating, entertaining, funny or educational, consider sending the writer a message.</p><h2 id="perspectives-on-developer-blogging-from-other-people">Perspectives on developer blogging from other people</h2><p>If you're interested in anything monetization related (picking up your topics based on what brings audience, making money with blog, SEO, etc), I recommend checking out <a href="https://bloggingfordevs.com/">Monica Lent's Blogging for Devs course and community</a>. <a href="https://www.stephaniemorillo.co/">Stephanie Morillo</a> also has incredible content for you, including <a href="https://www.stephaniemorillo.co/product-page/the-developer-s-guide-to-content-creation">The Developer's Guide to Content Creation</a>.</p><p>Sara Soueidan's <a href="https://www.sarasoueidan.com/desk/just-write/">Just write.</a> article is a good source of inspiration and reminder for writing when you have something to share, even if it's not the biggest and most researched article.</p><p>Morten Rand-Hendriksen's <a href="https://mor10.com/blogging-is-dead-long-live-ephemerality/">Blogging is Dead. Long live ephemerality.</a> is one of those pieces that I refer to constantly. It's a great commentary on how platforms like Instagram and TikTok has changed the scene and also why blogging is a great format for content that can live for a long time and be accessed and referred to easily.</p><p>Two blog posts that have helped me during my internal struggles about my topics and audience this year are Rach Smith's <a href="https://www.nateliason.com/blog/be-yourself">To be whole is the goal</a> and Nat Eliason's <a href="https://www.nateliason.com/blog/be-yourself">Be Yourself, Not a Niche</a>. They both talk about how we are more than just the niche content we might aim to create and why it's perfectly reasonable and great to have a personal blog that explores different aspects and interests of your life.</p><p>Leticia Portella's <a href="https://leportella.com/why-have-a-blog.html/">Why you should have a blog (and write in it)</a> is another good article about motivations and benefits for blogging and also ideas for what to write about.</p><p>However, blogging isn't always just sunshine and flowers. Eeva-Jonna Panula wrote a good blog post about <a href="https://eevis.codes/blog/2021-09-09/the-dark-side-of-blogging/">The Dark Side of Blogging</a> where she brings up topics like self-pressure and the negative response you might get from your writing.</p><p>And not a blog post about blogging but an example of very different style, I always want to bring up Ruksi Laine's <a href="https://ruk.si/notes">/notes</a> page that is more of a collection of notes than a prose-style blog. It's a brilliant example of how you can reach the benefits of blogging without having to write long posts.</p><p>And as Seth Godin says in his post <a href="https://seths.blog/2020/12/the-most-important-blog-post/">The most important blog post</a>: <em>"And the most important post? The one you’ll write tomorrow."</em></p>
Python 3.10 is out and I'm excited
2021-10-06T00:00:00Z
https://hamatti.org/posts/python-3-10-is-out-and-im-excited/
<p><em>If you're here because it's the first Wednesday of the month and you're looking forward to a Learning Rust blog post, I'm gonna have a break month from that this time because I haven't written any Rust in the past month. Meanwhile, enjoy while I get excited about Python's newest release.</em></p><p>This Monday, October 4th Python 3.10 was finally released. It's the one programming language version release that I've been most excited ever and one of the first times I've used the new version before its launch. Here are the main two reasons for my excitement:</p><h2 id="pattern-matching">Pattern Matching</h2><p>The main reason I got excited about this release is the addition of <strong>pattern matching feature</strong>. I wrote <a href="https://hamatti.org/posts/pattern-matching-is-coming-to-python/">a blog post about it already in February</a> when it was announced and I recommend watching <a href="https://www.youtube.com/watch?v=6K7bdIRIqbU">Daniel Moisset's talk about the pattern matching from PyAmsterdam meetup</a> from last March and an early tutorial by <a href="https://py.watch/get-started-with-pattern-matching-in-python-today-ef4d19c97b7a?sk=ee1b6f2842aaeb3dbd83ed8debe5f95c">Alexander Hultnér: Get started with Pattern Matching in Python, today!</a></p><p>To illustrate what pattern matching provides, let's take a look at this example from the <a href="https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro">PEP 636</a>:</p><pre><code class="language-python"># point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")</code></pre><p>Pattern matching combines control flow (if/else) with destructuring of values. In the above example, the control flow is based on the values being 0 for both, 0 for first item, 0 for second item or anything else. The destructuring happens when we assign the variable information (x and y) to a programming language variable so we can use it inside the case block.</p><p>In <a href="https://www.youtube.com/watch?v=AHT2l3hcIJg">3.10 Release Stream</a>, Brandt Bucher showed a few examples of how the new functionality makes the code cleaner and easier to read, here's one:</p><pre><code class="language-python"># Python 3.10
match meal:
case entree, side:
...
# Python 3.9
if (
isinstance(meal, Sequence)
and len(meal) == 2
):
entree, side = meal
...</code></pre><p>I've been using <a href="https://hamatti.org/posts/learning-rust-pattern-matching/">pattern matching a lot with Rust</a> and I'm so happy it's finally available in Python. I believe it will make some parts of the code nicer to write and read in the long run. That means more focus on what the code achieves and less on how it's implemented.</p><h2 id="error-message-improvements">Error message improvements</h2><p>Something I've been following on Twitter as part of 3.10's development has been the work done on <a href="https://docs.python.org/3.10/whatsnew/3.10.html#better-error-messages">improving error messages</a>. Error messages in many technical products, whether they are programming languages, tools or services, are often quite cryptic and unless you already know what they mean, they be hard to understand and I've seen a lot of beginners just ignore them.</p><p>Here are some examples from the <a href="https://docs.python.org/3.10/whatsnew/3.10.html">What's New in Python 3.10 page</a>:</p><p><strong>Unclosed curly braces</strong></p><pre><code>File "example.py", line 1
expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
^
SyntaxError: '{' was never closed</code></pre><p><strong>Missing colon at the start of a block</strong></p><pre><code>>>> if rocket.position > event_horizon
File "<stdin>", line 1
if rocket.position > event_horizon
^
SyntaxError: expected ':'
</code></pre><p><strong>Missing commas</strong></p><pre><code>>>> items = {
... x: 1,
... y: 2
... z: 3,
File "<stdin>", line 3
y: 2
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?</code></pre><p><strong>IndentationErrors</strong></p><pre><code>>>> def foo():
... if lel:
... x = 2
File "<stdin>", line 3
x = 2
^
IndentationError: expected an indented block after 'if' statement in line 2</code></pre><p>And there are more. I'm sure these will have a big impact on especially new developers' journey with Python. Learning from helpful error messages is a great way to become a better developer and I'm thankful for the work Pablo Galindo and Batuhan Taskaya has put on these.</p><h2 id="the-other-stuff">The other stuff</h2><p>Python 3.10 also brings improvements on typing with <a href="https://www.python.org/dev/peps/pep-0604"><code>X | Y</code> union type declarations</a>, <a href="https://www.python.org/dev/peps/pep-0613">explicit type aliases</a> and <a href="https://www.python.org/dev/peps/pep-0612">parameter spefication variables</a>.</p><p>To find out about all the good stuff coming to Python with the newest release, <a href="https://docs.python.org/3.10/whatsnew/3.10.html">read the docs</a>. </p><h2 id="3-10-release-stream">3.10 Release Stream</h2><p>If you're interested in seeing what a release of a new version looks like, <a href="https://www.youtube.com/watch?v=AHT2l3hcIJg">watch the Python 3.10 Release Party recording from Monday</a>. It was really cool to see the process and what happens behind the scenes. They did a great job combining the actual "here's how we build and publish a new version" process with presentations on the new features. </p><p>I found it not only enjoyable but a really great way to understand what new features are being added, how they function and what were the design decisions behind them. </p>
How would you compare two version numbers?
2021-09-29T00:00:00Z
https://hamatti.org/posts/how-would-you-compare-two-version-numbers/
<p>Earlier this week, I ran into <a href="https://twitter.com/LeahNeukirchen/status/1442485142526693383">a tweet by Leah Neukirchen</a> who said <em>"You won't believe how much software breaks because Python 3.10 has a two-digit minor version." </em>In the discussion that followed, <a href="https://twitter.com/cimbuldev/status/1442507250359312389">Tim Yates mentioned using comparing two software version numbers as an interview question</a> and that started to linger in my mind.</p><p>Initially I thought that this can be quite a binary interview question: if you're familiar with the schemes of software versioning, you'll have a good shot but if you're like me and have only used the basic stuff and never had to compare them, you may feel like a fish out of the water.</p><p>In a good job interview setting, this would probably lead into discussion about the restrictions of version numbering systems that need to be supported, some discussion about how often standards are not always adhered or teams may use different versioning systems. And that would be an interesting discussion, I'd love to be part of one. But having been to job interviews were you're given a task and let alone to code for an hour, I'm not so optimistic and it leads to me approaching technical interview questions with a very different mindset. </p><h2 id="so-how-would-we-do-it-then">So how would we do it then?</h2><p>But more interesting than just talking about good or bad interviews, I want to talk about this problem and my thoughts on how I would approach it. I mentioned at the beginning that I'm not very familiar with versioning, especially at the level of comparing them programatically. I've only ever read and created them as a human, never with a computer.</p><p>A very common system for software versions is called <a href="https://semver.org/">Semantic Versioning or SemVer</a> when you're strapped with characters. On a very basic, it consists of three numbers separated by dots:</p><pre><code>x.y.z</code></pre><p>where <code>x</code> denotes a MAJOR version, <code>y</code> denotes a MINOR version and <code>z</code> denotes a PATCH version. This is what I know about version numbers without doing any other research so I would start by solving this.</p><p>I would split the string by <code>.</code>, convert each part to an integer and then compare them one by one from major to minor to patch until there's a difference. Maybe something like this:</p><pre><code class="language-python">def compare_versions(v1, v2):
"""Compare two version strings (in format x.y.z).
Returns RESULT.EQUAL if they are the same,
otherwise returns the one that is higher"""
if v1 == v2:
return RESULT.EQUAL
v1t = [int(num) for num in v1.split('.')]
v2t = [int(num) for num in v2.split('.')]
for v1v, v2v in zip(v1t, v2t):
if v1v > v2v:
return v1
elif v2v > v1v:
return v2</code></pre><p>First check if the strings are equal and if not, go through each version part (major, minor, patch) at the time, if they are different, return the version number that is bigger. </p><p>That wasn't so hard, was it? But like they say in infomercials, <em>that's not all.</em></p><h2 id="but-what-about-pre-release-and-build-info">But what about pre-release and build info?</h2><p>SemVer supports additional information for pre-release versions and metadata for build numbers. At this point, I needed to start doing research, something that most interview sessions probably would not include. But luckily, I'm not interviewing so I can go down this rabbit hole.</p><blockquote>A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version.</blockquote><p>So we learn that a pre-release version is optional and is separated from the version with a hyphen: <code>1.0.0-alpha</code> or <code>2.3.1-rc1</code>.</p><p>For comparing these, from the SemVer docs we learn two things:</p><blockquote>When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version:<br /><br />Example: 1.0.0-alpha < 1.0.0.</blockquote><p>and</p><blockquote>Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each dot separated identifier from left to right until a difference is found as follows:<br /><br />1. Identifiers consisting of only digits are compared numerically.<br /><br />2. Identifiers with letters or hyphens are compared lexically in ASCII sort order.<br /><br />3. Numeric identifiers always have lower precedence than non-numeric identifiers.<br /><br />4. A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.<br /><br />Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.</blockquote><p>For build metadata, SemVer says (emphasis mine):</p><blockquote>Build metadata MAY be denoted by appending a plus sign and a series of dot separated identifiers immediately following the patch or pre-release version. Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]. Identifiers MUST NOT be empty.<strong> Build metadata MUST be ignored when determining version precedence.</strong></blockquote><p>Alright, so our function needs to take these two into account when parsing and only consider pre-release version when comparing.</p><p><a href="https://regex101.com/r/vkijKf/1/">There's a regex for that</a></p><pre><code class="language-regex">^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
</code></pre><p>Yeah, I wouldn't come up with that in an interview.</p><h2 id="good-everyone-uses-semver-right">Good, everyone uses SemVer. Right?</h2><p>Well, obviously not. It's just one system out there amongst many others, some more well-defined than others.</p><p>Sometimes people may add a "v" before the version number to say it's a version, like <code>v1.2.0</code> or they may only use two digits like <code>1.5</code>.</p><p>They also may use pre-release versions but not in a way that SemVer defines their precedence. Or they are not separated by hyphens but rather just appended to the version number like <code>1.0b3</code>.</p><p>Or there might be revision numbers: <code>1.2.12.102</code><em>.</em></p><p>A bit from the outfield but branding-wise there might be versions like 95, 98, ME, 2000, XP, 7, 8 and 10. Good luck with those, someone might use that as their version number.</p><p>There's also <a href="https://calver.org/">CalVer</a> that uses time in different formats. For example, Ubuntu uses versions like <code>20.04.3 LTS</code>.</p><p><a href="https://www.python.org/dev/peps/pep-0440/">Python</a> uses versioning that is defined as <code>[N!]N(.N)*[{a|b|rc}N][.postN][.devN]</code>.</p><p>TeX has been using idiosyncratic version numbering system since version 3, "<em>where updates have been indicated by adding an extra digit at the end of the decimal, so that the version number asymptotically approaches π. This is a reflection of the fact that TeX is now very stable, and only minor updates are anticipated. The current version of TeX is 3.141592653</em>".</p><p>And then there are those who don't really follow any other system than "this feels like a good version number".</p><h2 id="okay-it-s-complicated">Okay, it's complicated</h2><p>Probably, if you're writing a version parser or comparing functionality, you know what system you are developing it towards. I only knew about the ones I listed above because I did a lot of googling while writing this blog post.</p><p>I wouldn't have even gotten through comparing pre-release versions in a job interview setting and would have mentioned something like "not everyone uses SemVer though so it's quite a wild wild west".</p><p>Why I wanted to write this post was two-fold: one, I really got excited about exploring different versioning systems (I find TeX's quite hilarious and creative, albeit not probably most human friendly). Two, I wouldn't have known to even talk about those details in an interview setting because I had never dealt with them on this level.</p><p>It took me 15 minutes to google and I could probably write a function to compare most of them now, especially the more defined ones. But depending on the interviewer and the other applicants, I don't know if I would have gotten the job.</p><p>That's why I hate most technical questions in job interviews. If you know the answer, it's easy and you can have a great in-depth discussion of details. If you have never encountered it, no matter how simple the problem eventually would be, you'd be stuck on the very surface level stuff.</p>
Where do you see yourself in five years?
2021-09-22T00:00:00Z
https://hamatti.org/posts/where-do-you-see-yourself-in-five-years/
<p>I was recently in a job interview where I got asked the question in the title. It's a classic question in many job interviews but luckily recently I've been asked it very little. Even though the question itself is a great one, I don't think job interviews are a great place for that.</p><p>I got a bit surprised by the question and started my answer with something like "We're currently in a pandemic and I don't even know what the world looks like next month..." and continued with talking about some of my long term aspirations.</p><h2 id="not-a-great-interview-question">Not a great interview question</h2><p>My biggest issue with this as an interview question is that there's rarely an answer that would be both <strong>honest</strong> and <strong>good</strong>. It's one of those classic interview questions that articles and books teach you how to answer to – and if you follow someone else's advice on how to answer about your future plans and aspirations, it might lead to great results in the interview process but it's probably not honest.</p><p>In all honesty, most of my long-term aspirations are not compatible with majority of the work I've done in the past nor in the ones I'll do in the future. It doesn't mean that I wouldn't be a great employee and I can already tell here to any current or potential employee that I'm not looking for a job that I can retire from in 30-40 years. </p><p>What I want to do in life is to run a non-profit technology school that is complementary with existing education facilities. Something that helps people learn to create and build things in a very practical form. I've been running and coaching in a lot of non-profit programming workshops (like <a href="http://railsgirls.com/">Rails Girls</a>, <a href="https://djangogirls.org/en/">Django Girls</a> and <a href="https://codebar.io/">codebar</a>) and there are so many things I've learned from those that I want to one day do on a larger scale. However, that dream is far away so meanwhile I'll use my skills in the job market to help developers become better in different ways. But it's the honest answer. Maybe not exactly in "5 years" but at least for me, the question of 5 years means long-term aspiration and not exactly in 5 years looking from the calendar.</p><p>Another reason why I think it's not a great question for job interviews is that if at any point of my life, I'd look back 5 years ago and try to remember what I thought I'd do, I would have been completely wrong. Nothing that I've done I could have even imagined 5 years earlier – although, I kinda dreamed of working in Silicon Valley in "5-10 years" but it ended up taking me 8 months from those discussions to reach. Five years ago, I would have never imagined we'd be in a pandemic. Seven years ago, I would have never imagined I'd be an international conference speaker. Heck, 16 months ago I couldn't have imagined that I'd be 2 weeks away from completing my one year anniversary of publishing a blog post every week.</p><p>It's all because regardless of my wild imagination, I couldn't imagine big enough. Five years, even 10 years seems so short that looking forward, it's hard to imagine that much progress but looking back, there's been so much that I've learned, improved, and been eager enough to experiment.</p><p>And that's why I'll rather live my life in very much shorter plans. I let the flow take me to different places and I want to give myself the opportunity to grab those opportunities even if they'd be against my long-term plans because they can lead to adventures I never could have imagined.</p><h2 id="but-it-s-a-great-mentorship-question">But it's a great mentorship question</h2><p>I did mention in the beginning how I think the question is great. And even after all what I wrote before, I think we should ask it more often. Not in the interview setting but rather in mentorship sessions.</p><p>Reflecting on your recent past and daring to dream into the future is something we (in general) don't do enough. In our day-to-day life, we're so overwhelmed with what's in front of us: work, family life, responsibilities and especially restrictions that it's hard to remember to think big every now and then.</p><p>Back in the day I read a book called <a href="https://www.goodreads.com/en/book/show/24416709">Become an Idea Machine</a> and I've used its exercise number 66 regularly and whenever I'm invited to host my Dream Workshop (like I'm doing next week with <a href="https://oulues.com/human-accelerator-program">OuluES's Human Accelerator program</a>) or to talk about my dreams, it's on my slide deck. Whenever I'm asked to speak with people running different public or private programs, I show it and ask people to consider it.</p><p>In all of its simplicity, it states:</p><blockquote>If you had absolutely no worries about money, and no fear, what are ten things you would do this week?</blockquote><p>And if I use that as the final slide, I usually make my own addition of "<em>and then go and do them". </em></p><p>I don't use it because life would be so simple that we can just ignore the realities and restrictions. That would be foolish of me to assume that. Life is hard and filled with those restrictions and if you are running some kind of program in public sector project, your resources are probably also very restricted (I've used this with university teachers a lot).</p><p>I use it because it's a wonderful mental exercise. It gives you the permission to dream the life you would like to have if you could. I don't mean thinking about "I would be rich and famous" type of dreaming. When I use this myself these days, I frame it a bit differently:</p><blockquote>If you could do anything, what would your week look like?</blockquote><p>I would think about things like when do I want to wake up and where. Would I like to cook and eat lunch at home or be able to travel and enjoy the local cuisine in different places? What would my work days look like, both in structure and content.</p><p>I've been extremely privileged and lucky that I've been able to make a lot of those things into reality over the years. But that's not even the point. Even a small change to better can be a huge improvement in mental health and happiness but it often requires this kind of starting point where we think about the optimal situation rather than looking at the future from the pinhole of current restrictions.</p><p>To me, the answer to that question is very much the core of the question in the title. Sure, in corporate world corporations want you to say something about how much you're gonna enjoy working for that company in five years and climb the corporate ladder (but not too much to threaten your manager's position).</p><h2 id="so-where-do-i-see-myself-in-five-years">So where do I see myself in five years?</h2><p>I have no clue and I'm very happy I don't.</p>
Roll and Write board games are awesome
2021-09-15T00:00:00Z
https://hamatti.org/posts/roll-and-write-board-games-are-awesome/
<p>What comes to your mind when someone mentions board games? In addition to having a lot of fun with friends (or arguing with family over Monopoly in the 90s), one thing that is iconic to board games is having a board and a lot of meeples and tokens and cards. For a long time for me, the more the merrier.</p><p>Roll and Write board games are quite an unique genre in board gaming. First, they are often very light on game pieces: there's usually <em>a board</em> (either a plastic one with dry markers or a pad of paper boards) and some kind of <em>a randomizer</em>, commonly either a deck of cards or some dice. Second, the board is often forcing the players to ponder risk: there's a limited amount of spaces to fill, the randomizer is unpredictable but playing it safe won't win you any games. Third, there's often very little interaction and even less direct conflict with other players. </p><p>To me, for a long time those characteristics meant that I didn't like Roll and Write games. I especially enjoy playing against other players (not a fan of co-op games either).</p><p>Recently though, especially during the pandemic and while thinking about how to get new people into table top gaming, I've found a lot of love for these types of games. The ones with no interaction scale beautifully. You can play with unlimited number of players and even with pre-recorded sessions which is great if you're having trouble catching sleep on a lonely Saturday night.</p><p>The classic example of Roll and Write is <a href="https://boardgamegeek.com/boardgame/2243/yahtzee">Yahtzee</a> which can be a fun quick game played anywhere at any time. It's also one of the simplest examples and probably won't excite most players to get interested in the genre so let's look at some more interesting ones. </p><h2 id="what-s-roll-and-write">What's Roll and Write? </h2><p>Let's start from the beginning: what are these games about and how do they play?</p><p>To illustrate, let's take a look at a game that has zero interaction and thus can be played with unlimited amount of players and even when livestreamed on Youtube or Twitch. The game is called <strong><a href="https://boardgamegeek.com/boardgame/251412/tour">On Tour</a> </strong>and in the game you need to plan a route for a band to tour around USA or Europe depending on the map you're playing.</p><p>In Roll and Write games, players have their playing boards / pads and they fill in, usually numbers or shapes trying to fill in the board with optimal results.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/08/on-tour-map-kalchio-bgg.jpeg" class="kg-image" alt="On Tour game board" /><figcaption>On Tour USA map board, <a href="https://boardgamegeek.com/image/4608040/tour">picture by kalchio in BoardGameGeek (CC BY-NC-SA 3.0)</a></figcaption></figure><p>Each round, you need to place a number on a state/country in the map. The numbers you place are determined by rolling two D10s (for example, 5 and 8) and combining those digits both ways (resulting in 58 and 85). Then, three cards are drawn specifying the regions (West, Central, East, North and South) where you can place the numbers and you're good to go.</p><p>You start placing numbers on the board, trying to build a route of connected numbers that go up in ascending order. You can only move to a space that's connected with white dots and only if that new space has a number higher than the previous one.</p><p>In the end, you score points based on the amount of places you visit with your band.</p><p>I learned to play the game by playing along with <a href="https://www.youtube.com/watch?v=zNweekiPdBg">Michelle and Ruel on a play along video</a>. After that, I've played a bunch of other play alongs on Youtube.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/08/on-tour.jpeg" class="kg-image" alt="A filled in map of On Tour with 23 states visited and score of 31" /></figure><p>An example of one of my finished playthroughs, my band The Wild Alpacas did an East Coast to West Coast tour visiting 23 states and scoring 31 points while playing the tunes of the tundra. </p><h2 id="railroad-ink-another-no-interaction-game">Railroad Ink, another no-interaction game</h2><p>Another great example of the genre is <a href="https://boardgamegeek.com/boardgame/245654/railroad-ink-deep-blue-edition">Railroad Ink</a> and its expansions.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/08/railroad-ink-bgg-kalchio.jpeg" class="kg-image" alt="A 7-by-7 square board with a network of highways and railroads drawn by blue marker." /><figcaption>A filled in game of Railroad Ink Deep Blue, <a href="https://boardgamegeek.com/image/4608040/tour">picture by kalchio in BoardGameGeek (CC BY-NC-SA 3.0)</a></figcaption></figure><p>In Railroad Ink, the randomizer is 4 white dice representing different kinds pieces of railroad and highway (straight lines, T-intersections, turns and so on) and depending on the expansion, some colored dice with special features like rivers, lakes and meteors.</p><p>Each round, the dice are rolled and every player needs to draw those four sections into their 7x7 board, building a network of highways and railroads that connect to stations at the edge of the board. At the end of the game, you get points for most stations connected, longest routes and other factors.</p><p>Railroad Ink is a great example of the replayability and variety of the Roll and Write genre: even though each round every single player has exactly the same pieces to draw, the boards end up very different and there's very rarely any obviously best options that would make everyone draw the same. Both On Tour and Railroad Ink are played with hidden boards so you can't see what choices other players make.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/08/before-you-play-screenshot.jpg" class="kg-image" alt="A woman and a man sitting at a table, looking into the camera with Railroad Ink board game box in front of them" /></figure><p>If you want to test the game out, I highly recommend <a href="https://www.youtube.com/watch?v=DQJ5Xa8K3yo">Before You Play's play along in Youtube</a>. They do a great job explaining the rules and walking you through the game.</p><h2 id="copenhagen-more-involved-group-experience">Copenhagen, more involved group experience</h2><p>Where On Tour and Railroad Ink are infinitely scalable due to no interaction and hidden boards, that's not something everyone enjoys when playing board games with friends.</p><p><a href="https://boardgamegeek.com/boardgame/282463/copenhagen-roll-write">Copenhagen Roll and Write</a> is not only a lovely city in Denmark but also an interesting Roll and Write game that differs quite a lot with the previous two. In Copenhagen R&W, each player rolls dice with different colors on them and fills in a box of squares in their board based on their rolls with tetris pieces.</p><p>It's a Roll and Write adaptation of <a href="https://boardgamegeek.com/boardgame/269595/copenhagen/">a more traditional style board game of the same name</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/08/copenhagen-roll-and-write-bgg.jpeg" class="kg-image" alt="A board on top displaying different geometrical shapes and a bottom board with mid-game progress of Copenhagen" /><figcaption>A mid-game example of Copenhagen Roll and Write, <a href="https://boardgamegeek.com/image/4795145/copenhagen-roll-write">picture by W. Eric Martin in BoardGameGeek (CC-BY 3.0)</a></figcaption></figure><p>What makes Copenhagen R&W different from the other two is that it's played with open boards so you can always see how your opponents are doing and what they need to win. You also have interaction on each roll: every round a different player rolls dice, chooses some of them to use and the other players choose from the rest to advance their own board. The game is also played until someone scores 12 points compared to set amount of rounds in the previous two example games.</p><p>This means, you need your own friends to play the game with, no Youtube play alongs. However, if you want to learn the game and see how it's played, there's <a href="https://youtu.be/IePer3gUr7E">a really good video on BoardGameGeek's Youtube channel</a>.</p><h2 id="qwinto-all-about-them-maths">Qwinto, all about them maths</h2><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/08/qwinto-bgg-kalchio.jpeg" class="kg-image" alt="A Qwinto board with red, yellow and purple rows with empty slots for numbers" /><figcaption>A game board for Qwinto, <a href="https://boardgamegeek.com/image/3470617/qwinto">picture by kalchio in BoardGameGeek (CC BY-NC-SA 3.0)</a></figcaption></figure><p>If you want a quick pocket sized number based Roll and Write to put into your backpack for those coffee breaks or while sitting in the train, Qwinto is a really good choice. Its game board fits a small post card and plays quickly.</p><p>It shares a lot of mechanics from Yahtzee but is just so much better. Each round a player rolls 1-3 dice, gets a reroll if desired and then scores on the board, matching the color of the die to the rows in the score sheet. </p><p>The R&W magic comes from the risk taking and pushing your luck part: in each row you can place your number anywhere, but you can only write numbers in ascending order and on each column, you can have no repeated numbers. Suddenly, the simple mechanics turn into a challenge.</p><p>If you want a more entertaining example of why Qwinto is great (and couple of other Yatzhee-but-better examples), head over to <a href="https://youtu.be/mZ7rDuDWKFw?t=210">No Pun Included's Youtube channel</a>.</p><h2 id="rolling-japan-america-a-deviously-hard-rw">Rolling Japan/America, a deviously hard R&W</h2><p>Many of the Roll and Writes that I've played are rather chill. You might have to make a few hard choices during the game play but are not that often completely blocked off. That's not the case with <a href="https://boardgamegeek.com/boardgame/163377/rolling-japan">Rolling Japan</a> and its US version, <a href="https://boardgamegeek.com/boardgame/180198/rolling-america">Rolling America</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/08/rolling-america-bgg.jpeg" class="kg-image" alt="A box, a small cloth bag, 7 rainbow colored dice and a pad of game boards" /><figcaption>Rolling America game pieces, <a href="https://boardgamegeek.com/image/4859391/rolling-america">picture by SergiSan in BoardGameGeek</a></figcaption></figure><p>What makes these games hard is its limitations.</p><p>Each round, a player takes two of the seven dice (6 matching the colored sections in the map + white joker die), rolls them and the players then write those numbers somewhere in the map while matching their color. The gist is that each adjacent space can only be filled with number equal to its neighbor or one lower or higher. So if I put number 5 in a space, any space touching it needs to be either 4, 5 or 6.</p><p>With the way the maps are laid out, you'll start running into impossible rolls quite quickly. The winner of the game is the player with least unsuccessfully filled spaces. There are a few extra abilities like changing a color, guarding a space and duplicating your roll which allow strategic choices to overcome your competition but I haven't had such hard time filling a piece of paper since my university entrance exams than I have with Rolling Japan/America.</p><p>That said, it's still a fun and great game since every game is equally difficult to every player.</p><p>If you want to see how the game plays in real life, head over to <a href="https://www.youtube.com/watch?v=TdTybPYl8BE">Rahdo's Youtube channel for a great explanation</a>.</p><h2 id="i-only-touched-the-surface-here">I only touched the surface here</h2><p>These are just some of the examples of the genre. <a href="https://boardgamegeek.com/geeklist/213815/roll-and-write-games">BoardGameGeek has a community curated Roll and Write list</a> that at the time of writing has almost 300 Roll and Write games to explore.</p><p>One downside for Roll and Writes is that since you write or draw on the pieces every time you play, there are two main options for materials: either a stack of paper with the board printed on each which eventually leads to them running out and there being extra paper trash around, or a plastic one with dry markers that can be wiped clean after the game. In my experience, they tend to leave traces and over time, they become less nice to play with.</p><p>I've recently played a lot of these with my iPad. Either by using publisher/designer provided board PDFs (like in case of <a href="https://www.boardgametables.com/products/on-tour-usa-and-europe">On Tour</a>) or by taking a picture of the board and importing it to my note-taking app on iPad and drawing on top of it. This way, it's so much easier to make changes, fix errors and even share the screen with other players if ever needed.</p>
React Finland 2021 Recap
2021-09-08T00:00:00Z
https://hamatti.org/posts/react-finland-2021-recap/
<p><strong><a href="https://react-finland.fi/">React Finland 2022, in-person conference in Helsinki 12.-16.9.2022, tickets available now!</a></strong></p><p>Organizing – or attempting to organize – a tech conference hasn't been exactly an easy undertaking in the past years. Originally, this event was going to be hosted in May 2020 in Helsinki but due to the pandemic circumstances, ended up first being postponed to 2021 and eventually organized as an online conference last week. While most of us can't wait to be able to gather together in a physical conference again, I think the conference did a good job given the circumstances.</p><p>For transparency sake: I was in the event as the representative of Futurice, one of the gold sponsors.</p><p>I also did miss a couple of sessions during the week and obviously these are just my recommendations and picks based on what I'm interested and what kind of talks I enjoy and definitely not a ranking of any kind.</p><h2 id="miniconferences-leading-to-the-event">Miniconferences leading to the event</h2><p>When it became apparent an in-person event couldn't take place in 2020, the organizers decided to host a series of smaller monthly virtual events. Here are my picks from those sessions:</p><p><strong><a href="https://www.youtube.com/watch?v=BKf3oGfgLwk">Humanizing Your Documentation, Carolyn Stransky</a></strong></p><p>In the first mini-conference, Carolyn Stransky did a great talk about documentation. I really enjoyed her practical approach: the talk is well structured and provides great insights into how to write documentation that is helpful to your readers – who are humans. She focuses on the human side of reading the documentation. If you build software for other people to use, this 30 minute talk + 15 min Q&A and discussion is very much worth your time.</p><p><strong><a href="https://www.youtube.com/watch?v=kS0k8ul4Kc8">Lessons learned from burnout, Mikhail Novikov & Juho Vepsäläinen</a></strong></p><p>The realities of being a developer is so much more than just writing, reading and discussing code. I really like how React Finland has taken talks from wider perspective into the program as well (more on that later too) since if we don't talk about the other aspects when we, software developers, gather together in conferences, when are we gonna talk about them?</p><p>In this session, from the 4th mini conference, Mikhail Novikov and Juho Vepsäläinen have a great discussion about burnout which is way too common in our industry and what lessons they have learned from it.</p><p><strong><a href="https://www.youtube.com/watch?v=KdG-ylNxSlg">Tips and tricks for optimizing your React application, Nik Graf</a></strong></p><p>This talk comes from the 5th mini conference. Nik Graf goes through multiple tools and techniques – with examples from real life codebases – on how to investigate, measure and fix performance issues. I participated in one of Nik's React workshops back in 2019 and both in that and in this talk, learned so many new things into my toolbox.</p><h2 id="react-finland-2021-conference-picks">React Finland 2021 Conference picks</h2><p>All in all, the main conference had over 40 talks and demos, structured into sessions of 3-4 presentations each grouped by the topics. Compared to traditional way of just having streams of talks all day long, I really enjoyed this year's approach of having all the presenters in the same discussion before and between the presentations and during the Q&A. It made them feel more cozy and easier to approach.</p><p>I also wanna highlight the awesome job the MC crew did during these sessions. Juho Vepsäläinen, Manjula Dube and Sara Vieira were fantastic in bringing the sessions alive.</p><p><strong><a href="https://www.youtube.com/watch?v=RGQR79PbTFU">JSX for Designers, Travis Arnold</a></strong></p><p>I have an interest towards developer and designer collaboration having worked with incredible designers and one of the most interesting processes that have a lot of room for improvement is in the handoff and communication. Travis' talk this year was a really great look into how to approach it by creating design systems by using JSX as a shared source of truth across any platform.</p><p><strong><a href="https://www.youtube.com/watch?v=fgtS_nfMOtw">Web Components in React, Matias Huhta</a></strong></p><p>Matias was a guest of <a href="https://www.youtube.com/watch?v=kd6XtCPEpsA">my codebase livestream last June</a> and we discussed the basics of Web Components there. In this talk at React Finland, Matias did a great demo showcasing how to use Web Components in React projects and there was a nice discussion during the Q&A about how framework-agnostic Web Component components and libraries make it easier for new projects to be able to gain more traction when they don't have to build the entire community ecosystem from the scratch.</p><p>I also really liked the practical side of Matias' presentation. Live demos with web code usually involves the code editor, the browser, maybe a terminal and Javascript console and Matias' use of Codesandbox made it really easy to follow along.</p><p><strong><a href="https://www.youtube.com/watch?v=Y_lOg-rsqBM">Introducing state machines and statecharts, Laura Kalbag</a></strong></p><p>The entire statechart block was fantastic and all the three talks deserve a look. Laura started the session by providing a wonderful and easy to approach introduction to statecharts. Statecharts and using state machines to model the flow of the code is an interesting approach that I haven't really used that much yet but have been really interested in starting to use for a while now.</p><p><strong><a href="https://www.youtube.com/watch?v=zll9uDQOOq0">Make legacy code delightful with statecharts, Matt Pocock</a></strong></p><p>Working with legacy code can be messy. When you don't quite fully understand what the code intends to do, it can be hard to reason with it and if there aren't good tests to support the code, it's scary to make changes. That's why I really enjoyed this talk by Matt as he walked us through an example codebase and how to switch from <code>useEffect</code> style React code to using statecharts.</p><p><strong><a href="https://www.youtube.com/watch?v=9k1ZHHJWt7k">The Actor Model: a new mental model for React, Farzad YousefZadeh</a></strong></p><p>Finally, third in the statecharts block, Farzad talked about the actor model. I got introduced to the entire concept of using state machines and statecharts in programming by Farzad and have learned so much from his previous talks in different meetups and conferences.</p><p>Farzad explained the Actor Model architecture well and explained the benefits of using it when building applications with React.</p><p><strong><a href="https://www.youtube.com/watch?v=SgM2lEjCyAY">Opensource Documentation—Tales from React and React Native, Rachel Nabors</a></strong></p><p>One of the most anticipated talks for me was Rachel Nabors' talk on open source documentation. I'm a huge fan of documentation and constantly learning more to become better as both a technical writer and someone who manages and advocates for documentation in projects.</p><p>A couple of interesting pointers that I picked up from this talk is ideas for keeping track of the state of documentation and being mindful about the documentation rotting as it needs to be constantly maintained, and the big picture of the ecosystem of canonical documentation as source of truth combined with the community created resources like blog posts, courses and tutorials.</p><p><strong><a href="https://www.youtube.com/watch?v=CVjbrvsaJDw">How to Catch Low-Hanging Accessibility Fruits while Developing, Eevis Panula</a></strong></p><p>Accessibility is such a crucial part of building software that's usable for everyone – and it should be at the very core of the software and product development workflow but we're unfortunately still bit far from that. Eevis' talk about web accessibility is a very practical hands-on talk that shares tools and techniques that you can bring with you to the work the next day to start exploring the state of accessibility in your application or website. </p><p>I also recommend checking out her other talks and blog posts from <a href="https://eevis.codes/">eevis.codes</a>.</p><p><strong><a href="https://www.youtube.com/watch?v=joQUzBzLEJg">Continuous Localization in Enterprise Web Projects, Ante Tomić</a></strong></p><p>If you're building a web application that is aimed for users who are not all native speakers of the same language, localization can improve the lived user experience of using your service. In continuous deployment projects, you need a way to manage the localizations in the same cycles as well and Ante describes one way to achieve this on his talk.</p><p><strong><a href="https://www.youtube.com/watch?v=Ag5NBguCucc">Screenshot testing with ViteShot, François Wouts</a></strong></p><p>One aspect of automated testing that doesn't come up as often (at least in my circles) is visual regression testing. It's testing for changes in how the frontend is rendered – in plain English, it's taking screenshots of the output and comparing them after making code changes to make sure nothing unwanted changes accidentally.</p><p>François walks you through using ViteShot library to integrate screenshots and visual regression testing to your React projects.</p><p><strong><a href="https://www.youtube.com/watch?v=YRZN5a2py00">Design Systems of a Down: Steal this Guide!, m4dz</a></strong></p><p>If you want to start learning about design systems or refresh your memories about the basics and gain practical skills, this talk by m4dz is a great one to watch. As developers, our work intersects with design a lot and especially in the case of design systems, is crucial for implementing the vision of the designers in our teams.</p><p><strong><a href="https://www.youtube.com/watch?v=75orGgAl-F4">The future of work is enjoyable, Elisa Heikura</a></strong></p><p>Finishing of the conference, I really enjoyed Elisa's introspection on chasing one definition of success and being content: "once I get this one more thing, then I can..." I found the talk very easy to relate to and I think it served as a great finish for the conference. I won't spoil it too much, just go and watch it.</p><h2 id="afterword">Afterword</h2><p>These are only some of the great talks from this year's event that I enjoyed. You can find all the recordings from <a href="https://www.youtube.com/playlist?list=PL-a9lBflNu2opNHeISTnlHgGVlI7oGLXi">React Finland's Youtube channel</a> so go take a look at them as well.</p><p>Really hoping that next year's event can be organized in-person and I hope to see you there too.</p>
Learning Rust #8: What's next?
2021-09-01T00:00:00Z
https://hamatti.org/posts/learning-rust-8-whats-next/
<p>
<em>
Last December I finally started learning Rust and in January I built and
published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my monthly blog series that is defnitely
not a tutorial but rather a place for me to keep track of my learning and
write about things I've learned along the way.
</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community/">Learning Rust #7: Learn from the community</a>
</li>
<li>Learning Rust #8: What's next? (you're here)</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
It's been a while since I've written any Rust code. As the NHL season ended, I
did a few small adjustments to 235 and I have very small content update to be
done as the next season arrives (welcoming Seattle Kraken to the league!).
That means, I've focused on other things during the summer – mainly surviving
in the pandemic.
</p>
<p>
As the evenings have turned darker and more rainy, I've started to think more
about what I'd like to build next to take my learning journey with Rust to a
deeper level.
</p>
<p>
235 was a rather simple project: it fetches data from a single API endpoint
and prints it on the screen. And I loved it for that. But it meant I didn't
really have a chance to really explore the more advanced functionality with
Rust and I definitely didn't want to introduce any new features just for the
sake of adding them to learn.
</p>
<p>
In mid-June, I did
<a href="https://www.youtube.com/watch?v=v4t4pWIxz8I">a talk in Rust Denver meetup</a>
and we chatted briefly with Brooks about what my plans were next. Back then, I
had an idea of building my own static site generator as I really like them and
I had some ideas I wanted to explore in building a very custom tool for my own
website.
</p>
<p>
However, I think I'll shelf that idea for a while. My website is due to a full
rewrite in the near future and I'm focusing on making sure I get everything
out from <a href="https://11ty.dev/">Eleventy</a> as I plan it. One day I'll
probably build one though.
</p>
<h2 id="personal-grocery-management-system">
Personal grocery management system
</h2>
<p>
Today I was watching some videos on Youtube about urban planning and through
random browsing, ended up seeing
<a href="https://www.youtube.com/watch?v=kYHTzqHIngk">a video by Not Only Bikes on grocery shopping in Amsterdam</a>. In the video, he showed a handheld scanner that's used while shopping to
expedite the process.
</p>
<p>
That brought back some memories from my early adulthood as I was moving to
live on my own for the first time. Back then (late 2000s), I was thinking
about buying a barcode scanner and building a software to track the food I
bought and ate. I ended up never building it but the idea of using automation
to keep track of my purchases has stuck with me and this video sparked the
idea again.
</p>
<p>
There's a bunch of things that interest me which would make it a great next
learning project:
</p>
<ul>
<li>Building a web backend with Rust</li>
<li>Interacting with databases with Rust</li>
<li>Interacting with the barcode scanner in Javascript web app</li>
</ul>
<p>
Since I mostly work on the web envinronment, I want to explore the web backend
ecosystem of Rust (is Rocket the way to go or what's the favorite web
framework I should look into?) and since there was no data management in 235,
working with databases is something I want to get comfortable with in Rust.
I'm already comfortable with SQL databases like Postgres and the support looks
solid so I think it'll be a great idea.
</p>
<p>
For the barcode scanner part, I found
<a href="https://serratus.github.io/quaggaJS/">QuaggaJS</a> which on my first
experiments seemed to be quite ok solution for scanning items without having
to leave the web app. If you're interested in seeing how that works, I will be
writing a blog post about it outside this Learning Rust series so I recommend
subscribing to <a href="https://hamatti.org/feed/feed.xml">my RSS feed</a> so
you don't miss any new blog posts.
</p>
<p>
The idea in a nutshell is this: when I come home from a shopping trip, I'd
scan the products' barcodes as I put them into the fridge/pantry and the app
would keep track of when I've bought what and eventually give me insights into
my eating habits.
</p>
Syntax highlight all the things
2021-08-25T00:00:00Z
https://hamatti.org/posts/syntax-highlight-all-the-things/
<p>Almost every developer, who has seen syntax highlighted code in their editor, would never want to go back to monocolor coding. It's been a key feature in code editors for ages and a thing we don't often think about.</p><p>As a speaker and blogger however, I've ended up thinking about them quite a lot. I want to make the code on my blog posts and talk slides easy to read. For the web, one of the top libraries to provide syntax highlighting is called <a href="https://prismjs.com/">Prism.js by Lea Verou and contributors</a>. They have a huge collection of different languages and different themes so as a blogger, all you need to do is add a Javascript and a CSS file, add <code>class="language-javascript"</code> to your <code><code></code> blocks in HTML and Prism will take care of the rest.</p><p>I use Prism on this website as well and I've been very very happy with it.</p><p>But programming languages are not the only things that have structure you may want to highlight or colorize. One such format I've worked with a lot in my life is Pokemon TCG deck lists exported from/imported to Pokemon TCG Online software.</p><p>A deck list looks like this:</p><pre><code class="language-null">****** Pokémon Trading Card Game Deck List ******
##Pokémon - 16
* 1 Charmander VIV 23
* 1 Entei CEC 28
* 1 Heatmor RCL 34
* 1 Heatran LOT 48
* 1 Litwick RCL 31
* 1 Salandit SSH 27
* 1 Sizzlipede SSH 38
* 1 Tepig BLW 15
* 1 Centiskorch SSH 39
* 1 Charmeleon GEN 104
* 1 Lampent RCL 32
* 1 Pignite BLW 17
* 1 Salazzle UNB 31
* 1 Chandelure RCL 33
* 1 Charizard VIV 25
* 1 Emboar BLW 20
##Trainer Cards - 32
* 1 Marnie PR-SW 121
* 1 Ball Guy SHF 57
* 1 N FCO 105
* 1 Professor Juniper PLF 116
* 1 Evosoda GEN 62
* 1 Ultra Ball DEX 102
* 1 Pokémon Fan Club UPR 155
* 1 Timer Ball SUM 134
* 1 Lysandre FLF 90
* 1 Giant Hearth UNM 197
* 1 Welder UNB 189
* 1 Heavy Ball BKT 140
* 1 Scorched Earth PRC 138
* 1 Guzma BUS 115
* 1 Bird Keeper DAA 159
* 1 Quick Ball SSH 179
* 1 Escape Rope PLS 120
* 1 Stadium Nav UNM 208
* 1 Cynthia UPR 119
* 1 Colress PLS 118
* 1 Air Balloon SSH 156
* 1 Fire Crystal UNB 173
* 1 Brigette BKT 161
* 1 Tate & Liza CES 148
* 1 Rare Candy PLB 85
* 1 Blacksmith FLF 88
* 1 Float Stone BKT 137
* 1 Nest Ball SUM 123
* 1 Evolution Incense SSH 163
* 1 Level Ball NXD 89
* 1 Order Pad UPR 131
* 1 Trainers' Mail AOR 100
##Energy - 12
* 1 Heat {R} Energy DAA 174
* 1 Burning Energy BKT 151
* 10 Fire Energy GEN 76
Total Cards - 60
****** Deck List Generated by the Pokémon TCG Online www.pokemon.com/TCGO ******</code></pre><p>Most often you just copy-paste these from different sources into PTCGO, build yuor deck and start playing. But as I was writing my <a href="https://hamatti.org/gaming/the-best-way-to-play-pokemon-tcg/">gaming blog post on the Gym Leader Challenge format</a>, I wanted to make the deck lists a bit prettier.</p><p>So I decided to learn how to extend Prism.js with a custom syntax parser. I started from looking at <a href="https://prismjs.com/extending.html">the documentation page for extending</a>.</p><h2 id="extending-prism-js">Extending Prism.js</h2><p>There are couple of key structures in extending the Prism with custom definitions:</p><ol><li>Adding a language to <code>Prism.languages</code> by creating a new property that shares the name with what you want the name in <code>language-[name]</code> be. In my case, I added a new object into <code>Prism.languages.ptcgo</code></li><li>In simplest form, a parser is a collection of names and patterns, executed in order.</li></ol><p>For example, in the above deck list, the first and the last line start with a bunch of asterisk. </p><pre><code class="language-js">Prism.languages.ptcgo = {
ptcgo_title: /\*\*\*\*\*\*.*/
}</code></pre><p>This definition creates a <em>token</em> named <code>ptcgo_title</code>. I decided to go with prefix/namespaced tokens to make sure they don't accidentally overlap with other blocks.</p><p>Here's the current full definition. Compared to programming languages, there's way less flexibility on what people can do as the structure is very clearly formatted.</p><pre><code class="language-js">Prism.languages.ptcgo = {
ptcgo_title: /\*\*\*\*\*\*.*/,
ptcgo_subheader: /##.*/,
ptcgo_total: /Total Cards \- \d+/,
ptcgo_set: /([A-Z-]{3,5}|Energy) \d+/,
ptcgo_quantity: /\d+/,
ptcgo_card_name: /[A-Za-z'&{}é]+/,
};</code></pre><p>Since the format is quite unstructured in a way (it's just text), I had difficulties in differentiating for example the numbers and some of the text. With a bit of exploration and experimenting, I discovered that the tokenization happens in order of the definitions so by having the <code>ptcgo_total</code> before <code>ptcgo_card_name</code>, it doesn't match <code>"Total Cards"</code> into <code>ptcgo_card_name</code>. Same with the number in <code>ptcgo_set</code> compared to <code>ptcgo_quantity</code>. I'm not 100% sure if this is part of the spec or if I just got lucky.</p><p>This is all that's needed to get Prism.js to tokenize code in <code><code></code> blocks with a rather simple custom language definition. For more complex definitions, the library offers more options, of which you can learn at <a href="https://prismjs.com/extending.html">the official documentation</a>.</p><p>Combined with my CSS declarations for these, we get the same deck list as above but in color:</p><pre><code class="language-ptcgo">****** Pokémon Trading Card Game Deck List ******
##Pokémon - 16
* 1 Charmander VIV 23
* 1 Entei CEC 28
* 1 Heatmor RCL 34
* 1 Heatran LOT 48
* 1 Litwick RCL 31
* 1 Salandit SSH 27
* 1 Sizzlipede SSH 38
* 1 Tepig BLW 15
* 1 Centiskorch SSH 39
* 1 Charmeleon GEN 104
* 1 Lampent RCL 32
* 1 Pignite BLW 17
* 1 Salazzle UNB 31
* 1 Chandelure RCL 33
* 1 Charizard VIV 25
* 1 Emboar BLW 20
##Trainer Cards - 32
* 1 Marnie PR-SW 121
* 1 Ball Guy SHF 57
* 1 N FCO 105
* 1 Professor Juniper PLF 116
* 1 Evosoda GEN 62
* 1 Ultra Ball DEX 102
* 1 Pokémon Fan Club UPR 155
* 1 Timer Ball SUM 134
* 1 Lysandre FLF 90
* 1 Giant Hearth UNM 197
* 1 Welder UNB 189
* 1 Heavy Ball BKT 140
* 1 Scorched Earth PRC 138
* 1 Guzma BUS 115
* 1 Bird Keeper DAA 159
* 1 Quick Ball SSH 179
* 1 Escape Rope PLS 120
* 1 Stadium Nav UNM 208
* 1 Cynthia UPR 119
* 1 Colress PLS 118
* 1 Air Balloon SSH 156
* 1 Fire Crystal UNB 173
* 1 Brigette BKT 161
* 1 Tate & Liza CES 148
* 1 Rare Candy PLB 85
* 1 Blacksmith FLF 88
* 1 Float Stone BKT 137
* 1 Nest Ball SUM 123
* 1 Evolution Incense SSH 163
* 1 Level Ball NXD 89
* 1 Order Pad UPR 131
* 1 Trainers' Mail AOR 100
##Energy - 12
* 1 Heat {R} Energy DAA 174
* 1 Burning Energy BKT 151
* 10 Fire Energy GEN 76
Total Cards - 60
****** Deck List Generated by the Pokémon TCG Online www.pokemon.com/TCGO ******</code></pre><h2 id="find-it-in-github">Find it in GitHub</h2><p>If you want to add this custom PTCGO highlighting to your own website, you can find the extension in Github: <a href="https://github.com/hamatti/prism-extension-ptcgo">hamatti/prism-extension-ptcgo</a>. It's open sourced with MIT licence so do what you wish with it.</p>
Blogging is my new favourite refactoring tool
2021-08-18T00:00:00Z
https://hamatti.org/posts/blogging-is-my-new-favorite-refactoring-tool/
<p>I've mentioned a few times in talks and social media before but while writing my <a href="https://hamatti.org/posts/blog-post-filter-with-netlify-functions/">blog post on Netlify Functions</a>, I was again reminded by it so I decided to write a full blog post on the topic.</p><p><strong>Blogging is my new favourite refactoring tool.</strong></p><p>It's a bit exaggerated statement but for my use case, it works exactly like that. I don't write code full-time professionally anymore but I still code: I build software and scripts for myself, for events or online activities I run and sometimes for non-profit organizations. And since my methods of communicating, sharing and teaching tech related concepts is through blog posts, workshops and talks, I tend to write a lot about what I've built. Sometimes directly, sometimes indirectly.</p><p>During 2021, I've written a series of blog posts about my learning journey with Rust (<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">start here</a>) and I've used examples from the command-line tool I've been building. </p><p>When I write code, my mind prioritizes <em>function over form. </em>Essentially, I'm trying to write code that works and does a job. I then refactor it to be better but I'm still often in the development mindset and don't notice everything.</p><p>When I write a blog post, I'm focusing on explaining things to others. When I then pick up an example from my code, I often see things I didn't see before. Many times I've written "this could have been done better by X", only to realize I should just refactor that into the code directly and showcase that in the blog post.</p><p>I read my code very differently when I'm in the development mindset compared to the blogger mindset. I do a bit of extra research on the topics when I write about them so I'm learning new things to apply but I also look at the code in an isolation. </p><p>When I was writing my <a href="https://hamatti.org/posts/blog-post-filter-with-netlify-functions/">blog post on Netlify Functions</a>, I realized that instead of fetching the data on each click, I can just fetch it once during page load and run filters over it. It makes sense but I was so deep in the "can I learn how to make it work" pit that I didn't even think about it until I started explaining to readers what I did.</p><p>This extra learning and refactoring that happens is one of the reasons I think that <a href="https://hamatti.org/posts/you-should-start-a-blog-today/">You should start a blog today</a>. It even works if you never publish those blog posts to the public because the value is created during the writing and editing process.</p><p>And when you do start your blog, <a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed">make an RSS feed available</a> so people can subscribe to your blog and you don't have to rely on cutting through the noise of social media feeds and their algorithms. </p>
Your blog should have an RSS feed
2021-08-11T00:00:00Z
https://hamatti.org/posts/your-blog-should-have-an-rss-feed/
<p>
To kick off this year, I wrote
<a href="https://hamatti.org/posts/you-should-start-a-blog-today/">a blog post about why you should have your own blog</a>. Today I'm writing about the next step: making sure you have an RSS feed so
people can subscribe to your blog. I'll first talk about what is an RSS feed,
then why you'd want to have one and finally a bit about how to have one.
</p>
<h2 id="what-s-an-rss-feed">What's an RSS feed?</h2>
<p>
<a href="https://en.wikipedia.org/wiki/RSS">RSS feed</a> is a standardized way
of providing a way to subscribe to content in the Internet. It's an extension
of XML and it used to be more popular before the emergence of social media as
a way to keep following interesting content provided by websites or bulletins.
</p>
<p>
You can find mine in
<a href="https://hamatti.org/feed/feed.xml">https://hamatti.org/feed/feed.xml</a>
and as an example, it looks something like this:
</p>
<pre><code class="language-xml"><feed xmlns="http://www.w3.org/2005/Atom">
<title>Juha-Matti Santala | Community Builder. Adventurer. Dreamer.</title>
<subtitle/>
<link href="" rel="self"/>
<link href="https://hamatti.org/"/>
<updated>2021-07-20T21:00:00+03:00</updated>
<id/>
<author>
<name>Juha-Matti Santala</name>
<email>juhamattisantala@gmail.com</email>
</author>
<entry>
<title>Javascript Basics: Scope</title>
<link href="https://hamatti.org/posts/javascript-basics-scope/"/>
<updated>2021-07-20T21:00:00+03:00</updated>
<id>https://hamatti.org/posts/javascript-basics-scope/</id>
<content type="html"> -- redacted for brevity --</content>
</entry>
</feed></code></pre>
<p>
Every time my blog is updated, so is my <code>feed.xml</code> file and anyone
subscribing to my feed will get an update that there's new content from me. In
my opinion, it's probably the best way to follow content (more on that later).
</p>
<p>
Many blogs are written and published in some sort of publishing or content
management system (like <a href="https://ghost.org/">Ghost</a> or
<a href="https://wordpress.org/">Wordpress</a>) or built with site generators
(like <a href="https://www.11ty.dev/">Eleventy</a> or
<a href="https://www.gatsbyjs.com/">Gatsby</a>). These tools offer automated
ways to generate your RSS feed so you don't have to manually type the XML each
time.
</p>
<h2 id="why-an-rss-feed-is-so-important">Why an RSS feed is so important?</h2>
<p>
These days, we (as readers) find a lot of the content in social media. Social
media can be a good tool to find new content and new writers but social media
platforms are run by algorithms that take the control of what content you see
away from you and they show the content they think is most addictive so you'll
spend more time on the platform.
</p>
<p>
This means that even following your favorite writers in Twitter or LinkedIn
doesn't mean that you'd always see what they publish.
</p>
<p>
However, subscribing to an RSS feed using an RSS reader (like
<a href="https://feedly.com/">Feedly</a>) or Slack's built-in
<code>/feed</code> functionality (I use this to keep track of when my
colleagues publish new posts as I get a ping in my work Slack) means that
you'll be in the driving seat. You choose which content you subscribe to and
you'll only see that content.
</p>
<p>
From author's point of view, it also means you can cut through all the noise,
circumvent the algorithm's arbitrariness and provide a nice way for your
readers to read your blog posts.
</p>
<p>
I follow a lot of developers and community builders on Twitter and I've
networked with quite a few on LinkedIn as well. I see a lot of interesting
blog posts there and find things to read. But when I find something I like, I
rather subscribe to their RSS to make sure I don't miss any of the new posts.
</p>
<p>
I've been collecting software developer's websites in a bookmark folder for
quite a while. Mostly blogs but also portfolio style sites: there's always
some new inspiration to find from those when improving my own site. The other
day, I wanted to add all of those to my new RSS reader as I got an iPad.
</p>
<p>
Out of the 60 blogs on my bookmark folder, only 32 had RSS feed. And around 6
or 7 of those didn't mention that anywhere on the website, I had to dig it
from the source (these were probably added automatically by their blogging
tool). With 60 (and counting) blogs, there's no way I'd check them out
manually with any kind of regularity.
</p>
<p>
So when I sit in the tram while commuting (yeah, it's still a thing) or when I
travel somewhere and I open my iPad, only roughly a half of these blogs end up
on my reading list. I do use <a href="https://getpocket.com/">Pocket</a> as a
secondary way of keeping track of things to read when there are things I don't
want to subscribe to but that means that if your blog doesn't have an RSS
feed, I'm probably not returning to see what new things you've written.
</p>
<p>
The great thing about having an RSS feed is that you own your content
delivery. Not Twitter, not LinkedIn, not [insert-your-favorite-platform]. If
you build your readership through RSS, you're not relying on a whim of a
middleman.
</p>
<p>
You can also decide how much you want to share through RSS: many decide to
share the entire article so it can be read completely through the reader while
others only provide an excerpt and have you actually go to their blog to read
it in full.
</p>
<p>
For me, a small win is also that I don't know how many people have subscribed
to my RSS feed. I don't have to obsess over likes or analytics, but rather
distance myself from that popularity contest and focus on providing
interesting thing for people to read.
</p>
<h2 id="how-to-add-and-rss-feed-to-your-blog">
How to add and RSS feed to your blog?
</h2>
<p>
There are two steps to adding an RSS feed: creating the feed and updating it
when new posts are written, and making that feed accessible to readers.
</p>
<h3 id="creating-the-feed">Creating the feed</h3>
<p>
You can create your feed manually if you wish but it's probably not a
sustainable way – before or later you'll forget to update it or grow tired of
doing it manually.
</p>
<p>
Here are links to documentation on some platforms and tools. If your tool
isn't mentioned, try googling <em>"[your tool] RSS" </em>and see if
documentation can be found.
</p>
<ul>
<li>
<a href="https://wordpress.org/support/article/wordpress-feeds/">Wordpress</a>
</li>
<li><a href="https://ghost.org/integrations/custom-rss/">Ghost</a></li>
<li>
<a href="https://support.google.com/blogger/answer/97933?hl=en">Blogger</a>
</li>
<li><a href="https://www.drupal.org/node/2426139">Drupal</a></li>
<li><a href="https://www.11ty.dev/docs/plugins/rss/">Eleventy</a></li>
<li>
<a href="https://www.gatsbyjs.com/docs/how-to/adding-common-features/adding-an-rss-feed/">Gatsby</a>
</li>
</ul>
<h3 id="adding-your-feed-to-the-website">Adding your feed to the website</h3>
<p>
I highly recommend having a link somewhere on your site that indicates to the
reader that there is an RSS feed. As I mentioned previously during my
experiment, roughly 20% of the blogs that had a feed, didn't mention it
visibly on the website.
</p>
<p>In my website's case, I'd add a link like this one:</p>
<pre><code class="language-html"><a href="/feed/feed.xml">RSS</a></code></pre>
<p>
Many (like my site too) also use the
<a href="https://en.wikipedia.org/wiki/RSS#/media/File:Feed-icon.svg">RSS icon</a>
to indicate a link to the feed.
</p>
<p>
There's also another way to expose your RSS feed to the readers:
auto-discovery. It means that in the <code><head></code> of your
website, you tell RSS readers where to find your feed. In my case, it looks
like this:
</p>
<pre><code class="language-html"><link
rel="alternate"
type="application/rss+xml"
title="hamatti.org &raquo; Feed"
href="https://hamatti.org/feed/feed.xml"
/>
</code></pre>
<p>
For your own use, replace <code>title</code> and <code>href</code> attribute
with your website's info.
</p>
<p>
With the auto-discovery link, your users can just use your top level URL to
add it to their RSS reader and they don't have to go hunting for the direct
link.
</p>
<h2>Other people's writing on the topic</h2>
<ul>
<li>
<a href="https://rknight.me/please-expose-your-rss/">Please, expose your RSS by Robb Knight</a>
</li>
<li>
<a href="https://journal.paoloamoroso.com/why-your-blog-still-needs-rss">Why your blog still needs RSS by Paolo Amoroso</a>
</li>
<li>
<a href="https://chriscoyier.net/2024/01/13/exposed-rss/">Exposed RSS by Chris Coyier</a>
</li>
<li>
<a href="https://www.learnwithjason.dev/blog/i-miss-rss/">I miss RSS by Jason Lengstorf</a>
</li>
</ul>
Learning Rust #7: Learn from the community
2021-08-04T00:00:00Z
https://hamatti.org/posts/learning-rust-7-learn-from-community/
<p>
<em>Last December I finally started learning Rust and in January I built and
published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my monthly blog series that is defnitely
not a tutorial but rather a place for me to keep track of my learning and
write about things I've learned along the way.</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust
</a>
</li>
<li>Learning Rust #7: Learn from the community (you are here)</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
One of the original reasons I got excited to start learning Rust was the
community I saw form around it. As I've mentioned before multiple times, I've
been mostly working with Javascript and Python and spending time in those
communities but still, Rust has been popping up on my radar constantly.
Whether it's been conference and meetup talks, blog posts or general
excitement about the language and its ecosystem in Twitter, there was always
something that caught my attention.
</p>
<p>
In this blog post, I want to share some of the resources that I've found very
helpful in my learning journey.
</p>
<h2 id="this-week-in-rust">This Week in Rust</h2>
<p>
The number one place for me to stay up to date and find new blog posts,
articles and videos about Rust is
<a href="https://this-week-in-rust.org/">the This Week in Rust newsletter</a>.
It's a weekly newsletter (you can either subscribe to it via email or via RSS
or just read it on the website) with a ton of links to community resources.
</p>
<p>
Each week, you'll have more stuff to read and watch than you probably have
time to. I often browse through the headlines, save interesting ones to Pocket
and read them whenever I have extra time.
</p>
<p>
If you're a reader of this blog series, it's also very likely you've found
your way to these blog posts earlier through this newsletter as it's been the
single largest contributor to my website visits.
</p>
<h2 id="twitter">Twitter</h2>
<p>
In Twitter, the hashtag
<a href="https://twitter.com/search?q=%23rustlang&src=typed_query">#rustlang</a>
is a great way to find tweets and to participate in the discussion. I've
gotten to know a lot of great rustaceans through sharing and participating in
#rustlang discussion. My experience with the community in Twitter has been
really great.
</p>
<p>If you wanna follow some accounts, here are some I'm always delighted by:</p>
<ul>
<li>
<a href="https://twitter.com/rustlang">@rustlang</a>, the official Rust
language account that also retweets a lot of Rust posts
</li>
<li>
<a href="https://twitter.com/ThisWeekInRust">@ThisWeekInRust</a>, the
account for the aforementioned newsletter
</li>
<li>
<a href="https://twitter.com/CecileTonglet">@CecileTonglet</a>, a rustacean
from who I've learned so much through her Tweets and sharing of her own
development
</li>
<li>
<a href="https://twitter.com/nellshamrell">@nellshamrell</a>, the board
director of the Rust Foundation
</li>
<li>
<a href="https://twitter.com/timClicks">@timClicks</a>, the author of Rust
in Action book, blogger and educator
</li>
<li>
<a href="https://twitter.com/m_ou_se">@m_ou_se</a>, the Rust Library team
lead
</li>
<li>
<a href="https://twitter.com/Brooks_Patton">@Brooks_Patton</a>, streams Rust
development, runs Denver Rust meetup
</li>
<li>
<a href="https://twitter.com/rust_foundation">@rust_foundation</a>, the
Twitter account of Rust Foundation (as is the foundation) is still quite
young so I don't know exactly what they'll share but I assume it'll be a
good way to stay up to date what happens with them
</li>
</ul>
<h2 id="video-stuff">Video stuff</h2>
<p>
There are also great ways to learn in video format, whether it's
Youtube/Twitch style content or event talk recordings. Here are some starting
points:
</p>
<ul>
<li>
<a href="https://www.youtube.com/c/RustVideos/">Rust @ Youtube</a>, curated
videos by the Rust team, including conference talks, RFC team videos, and
others
</li>
<li>
<a href="https://www.youtube.com/channel/UCBdn2g49kLEyjlssavwbxdQ">RustLab Conference @ Youtube</a>, an Italian Rust conference channel with also some remote meetup style
sessions recently
</li>
<li>
<a href="https://www.twitch.tv/brookzerker">Brooks Patton @ Twitch</a>,
regular live streams of building software with Rust
</li>
<li>
<a href="https://www.youtube.com/playlist?list=PLrmY5pVcnuE9amSHQCNl1bZCyJT2FziMy">Rust Denver playlist @ Youtube</a>, Rust Denver meetup recordings
</li>
<li>
<a href="https://www.youtube.com/channel/UClny6qj9Mv7uFo9XGUGYQBA">TimClicks @ Youtube</a>, Rust videos by Tim McNamara
</li>
<li>
<a href="https://www.youtube.com/channel/UCSp-OaMpsO8K0KkOqyBl7_w">Let's Get Rusty @ Youtube</a>, weekly bite-sized Rust videos
</li>
</ul>
<h2 id="rust-online-offline-communities">
Rust online & offline communities
</h2>
<p>
There are several places online for rustaceans to hang out like
<a href="https://users.rust-lang.org/">Users Forum</a>,
<a href="https://internals.rust-lang.org/">Internals Forum</a>, and
<a href="https://discord.gg/rust-lang">Discord Server</a>. There are also many
local Rust meetups you can find from
<a href="https://calendar.google.com/calendar/u/0/embed?showTitle=0&showPrint=0&showTabs=0&showCalendars=0&mode=AGENDA&height=400&wkst=1&bgcolor=%23FFFFFF&src=apd9vmbc22egenmtu5l6c5jbfc@group.calendar.google.com&color=%23691426&ctz=Europe/Madrid">the calendar</a>
or <a href="https://www.meetup.com/find/events/?keywords=rust">Meetup.com</a>.
</p>
<p>
I've also found great help and people to chat with from non-specific groups
like with <a href="https://futurice.com/">my colleagues</a> or
<a href="https://koodiklinikka.fi/">the Finnish Koodiklinikka community</a>.
So if you're involved in any general tech communities like those, take a look
if there's a group for rustaceans – or maybe start one.
</p>
<h2 id="what-did-i-miss">What did I miss?</h2>
<p>
I'm always looking to expand my network of sources to learn more. Let me know
<a href="https://twitter.com/hamatti">in Twitter</a> if you have good
suggestions!
</p>
<h3 id="recommendations-from-other-people-">
Recommendations from other people:
</h3>
<ul>
<li>
<a href="https://dygalo.dev/blog/rust-for-a-pythonista-1/">Dmitry Dugalo's 3-part series on using Rust with Python</a>
(recommended by<a href="https://twitter.com/benjaoming">
Benjamin Balder Bach</a>)
</li>
<li>
<a href="https://fasterthanli.me/tags/rust">Amos's blog posts on Rust</a>
(recommended by
<a href="https://twitter.com/Stranger6667">Dmitry Dugalo</a>)
</li>
<li>
<a href="https://www.youtube.com/c/RyanLevicksVideos">Streaming Rust with Ryan Levick</a>
(recommended by
<a href="https://twitter.com/Stranger6667">Dmitry Dugalo</a>)
</li>
<li>
<a href="https://www.youtube.com/c/JonGjengset/videos">Jon Gjengset (Youtube)</a>
(recommended by
<a href="https://twitter.com/Stranger6667">Dmitry Dugalo</a>)
</li>
</ul>
<p></p>
<p></p>
Blog post filter with Netlify Functions
2021-07-28T00:00:00Z
https://hamatti.org/posts/blog-post-filter-with-netlify-functions/
<p>I'm so excited. Last week, I wrote and deployed my very first serverless functionality: filtering my blog posts by category. You can test it out at <a href="https://hamatti.org/blog">/blog</a> main page.</p><h2 id="the-idea">The Idea</h2><p>I have quite a few blog posts on this website. On the current list, there's way over 100 posts and I wanted to add a way to filter them by category since the variety of posts in my blog is quite a lot and not everyone is interested in everything.</p><p>My current stack is this: blog posts are either in local markdown files or written in headless Ghost CMS system, the website is built with Eleventy static site generator and it's all hosted in Netlify.</p><p>I wanted to maintain the simplicity of static sites and my stack as much as possible.</p><p>Since I'm already hosted in Netlify, I decided to use Netlify Functions for my serverless platform since that enabled me to maintain a single deployment strategy.</p><p>So what I wanted to do, is to have a single Netlify Functions endpoint that returns me the info on blog posts that my blog listing needs: mainly their title, slug, excerpt, date and, most importantly for this feature, tags.</p><h2 id="the-implementation">The Implementation</h2><p>Due to my workflow, there are three steps to this implementation. I could skip one step by calling Ghost directly from the Netlify Function but one reason I really like the static nature of the site is that it has less dependencies at run-time. I already introduce one via Functions, I don't want to introduce two at the same time.</p><h3 id="step-0-create-a-function">Step 0: Create a Function</h3><p>Using Netlify Functions was a great experience. Thanks to their CLI toolkit (mainly <code>functions:create</code>, <code>dev</code> and <code>deploy</code>), I was able to set everything up in a few commands.</p><p>First, I ran <code>netlify functions:create</code> to use a guided interactive process to get a template file in a proper folder. In my case I created a <code>netlify/functions/</code> folder in the root of my project.</p><p>Second, I added a section to my Netlify config file:</p><pre><code class="language-toml">[functions]
directory = "netlify/functions/"</code></pre><p>To make sure my skeleton function was working, I booted up a dev server with <code>netlify dev</code> and tested it by running the function in <code>/.netlify/functions/[function_name]</code></p><h3 id="step-1-fetch-posts">Step 1: Fetch posts</h3><p>I have a very manual workflow for writing and publishing my blog posts: I don't use any automatic integrations but always fetch my posts by hand (using custom Node script) and deploying by hand. For updating the serverless function, I created a second Node script that fetches all the needed info for my posts from Ghost and writes them into a Javascript file.</p><p>The main logic looks like this </p><pre><code class="language-javascript">api.posts.browse({ limit: "all", include: "tags" }).then((posts) => {
const mainPosts = posts.filter((post) => !isGaming(post) && !isDraft(post));
const taggedPosts = mainPosts.map((post) => {
const { title, slug, tags, excerpt } = post;
return {
title,
slug,
excerpt,
tags: tags.map((tag) => tag.name),
};
});
const template = `module.exports = ${JSON.stringify(taggedPosts)}`;
fs.writeFileSync("netlify/functions/get_posts/posts.js", template);
});</code></pre><p>I'll run this everytime I make changes to my blog post tags in Ghost.</p><p>A one downside is that this approach currently only shows blog posts written in Ghost and none of my old Markdown posts. I'll maybe fix that when I make a larger rewrite of my website one day.</p><h3 id="step-2-netlify-function">Step 2: Netlify Function</h3><p>Now that I have all my post info in a Javascript file, hosting that to the caller via a serverless function is simple: read the data and return it.</p><pre><code class="language-javascript">const posts = require("./posts");
const handler = async (event) => {
try {
return {
statusCode: 200,
body: JSON.stringify({ posts }),
};
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};
module.exports = { handler };
</code></pre><p>Now, after each deploy, I can call a single endpoint that returns the newest data and use that to filter posts in the blog.</p><h3 id="step-3-add-filtering-option-to-website">Step 3: Add filtering option to website</h3><p>In my <code>blog.njk</code>, I added a list of buttons for each category I wanted to include. It's hardcoded since I figured I mainly want to showcase certain tags.</p><p>When the page is loaded, I fetch all the posts from Netlify Functions and store them.</p><p>If a user clicks the "All" category, I simply do a page refresh to return to the original list of all posts, divided by years and months. If they click any of the actual tags, I filter the posts and only show the ones with relevant tag.</p>
Javascript Basics: Scope
2021-07-21T00:00:00Z
https://hamatti.org/posts/javascript-basics-scope/
<p>One of the more abstract concepts a developer needs to understand quite early into their development journey is the concept of <strong>scope</strong>. In a simplified way, scope means <strong>when is a variable available to be used. </strong>If a variable is not <em>in scope</em>, you cannot refer to it. Let's see how this works in practice in Javascript. We'll start with the base rules and then look at some of the exceptions and how they work.</p><h2 id="the-baseline">The baseline</h2><p>The best way to think about scope is this:</p><blockquote>A variable enters the scope when it's created and exits the scope when the block ends. While it is in the scope, you can refer to it.</blockquote><h3 id="creating-a-variable">Creating a variable</h3><p>A variable is created in Javascript with one of the three keywords: <code>var</code>, <code>const</code> or <code>let</code>. So when you see a line <code>let year = 2021</code>, the variable <code>year</code> enters the scope. From this line onwards, you can refer to <code>year</code> inside your code.</p><p>If you try to access a variable before it's been created (or <em>initialized</em> as the lingo goes), you'll get a <code>ReferenceError: year is not defined</code> error.</p><p>There is a special case in Javascript for variables created with the keyword <code>var</code>, I'll talk about that a bit later under the subheading Hoisting. </p><h3 id="block">Block</h3><p>Above I also mentioned the variable exiting the scope when "the block ends". A block starts with an open curly brace <code>{</code> and ends with a closed curly brace <code>}</code>. Examples of blocks you've probably already seen are functions, if clauses and for loops but you can also create a block with just the curly braces if you ever want to create a block with local scope.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">function add(x, y) { // block starts here
let sum = x + y;
return sum;
} // block ends here, variables x, y and sum are no longer in scope
let year = 2021
if(year === 2021) { // block starts here
console.log(`It's 2021`);
} // block ends here
{ // block starts here
let x0 = 1;
let x1 = 2;
console.log(add(x0, x1));
} // block ends here, variables x0 and x1 are no longer in scope
</code></pre><figcaption>A few examples of blocks starting and ending in Javascript</figcaption></figure><p>The crucial part of understanding the concept of block is that any variable created inside that block will cease to exist when the block ends.</p><h2 id="functions">Functions</h2><pre><code class="language-javascript">function createPerson(name, birthYear, hobbies) {
let currentAge = 2021 - birthYear;
let person = {
name: name,
age: currentAge,
birthYear: birthYear,
hobbies: hobbies
};
return person;
}
const molly = createPerson("Molly", 1992, ["hiking", "photography"]);
console.log(molly)
// prints out
// { name: "Molly", age: 29,
// birthYear: 1992,
// hobbies: ["hiking", "photography"] }
console.log(person) // ReferenceError</code></pre><p>In the above example, we have two blocks: one for function <code>createPerson</code> and the other one containing all the rest (including the function definition).</p><p>Any variable that's been defined either in the function definition (parameters: <code>name</code>, <code>birthYear</code> and <code>hobbies</code>) or inside the function body (<code>currentAge</code> and <code>person</code>) are <strong>only accessible inside that function body. </strong>On the last line, we try to print out <code>person</code> but we get <code>ReferenceError</code> because <code>person</code> is not defined in that scope.</p><p>This is crucial in understanding how the data flows in Javascript: to make something available inside a function, you need to pass it into it via arguments and to make something from inside a function available outside it, you need to pass it back via <code>return</code> statement.</p><p>There's another way of creating a function in Javascript too but the same base rules apply to it:</p><pre><code class="language-javascript">const createPerson = (name, birthYear, hobbies) => {
let currentAge = 2021 - birthYear;
let person = {
name: name,
age: currentAge,
birthYear: birthYear,
hobbies: hobbies
};
return person;
}</code></pre><h2 id="exceptions-to-the-baseline">Exceptions to the baseline</h2><p>Understanding the baseline above will get you through most of the use cases and they are applicaple to most other programming languages as well. But it's valuable to understand the exceptions too for the moments when you run into them in code or need to use them yourself.</p><h3 id="hoisting">Hoisting</h3><p>The first exception to the rule is called <em>hoisting. </em>It's a concept in Javascript describing how the compiler allocates memory before executing the code. But for practical purposes, what you need to know is how sometimes the declaration and initialization of a variable changes and how functions are hoisted.</p><h4 id="variables">Variables</h4><p>Variables defined by <code>var</code> keyword are <em>hoisted</em> while ones defined by either <code>const</code> or <code>let</code> are not. The latter two are more recent addition to the language and partly for this very reason.</p><pre><code class="language-javascript">console.log(message); // prints undefined
var message = "Message is gonna be hoisted";</code></pre><p>In our above example, we don't get the <code>ReferenceError</code> we'd get previously for trying to access a variable that's not in the scope. That's because here's what this code ends up being before it's ran:</p><pre><code class="language-javascript">var message;
console.log(message);
message = "Message is gonna be hoisted";</code></pre><p>Javascript <em>hoists </em>the declaration of the variable to the top but not its definition so its value will be <code>undefined</code> until line 3.</p><p>The reason hoisting of variables can be a problem is that variables defined with <code>var</code> can be redeclared in the same scope:</p><pre><code class="language-javascript">var year = 2021;
var year = 2022;</code></pre><p>This can become a problem if you have a long, complex code where you accidentally end up reusing a variable name.</p><p>For example's sake, let's say you have code like this.</p><pre><code class="language-javascript">var isAllowed = checkUserPermissions(user); // let's say it returns false
// 30 lines of code
if(isAllowed) { openDoors(); }</code></pre><p>Now, later on, someone needs to add a piece of code in between:</p><pre><code class="language-javascript">var isAllowed = checkUserPermissions(user);
// 30 lines of code
var userList = [ { id: 1 }, { id: 2 }, { id: 3 }];
for (var user of userList) {
var isAllowed = user.id > 2;
user.isAllowed = isAllowed;
}
if(isAllowed) {
openDoors();
}</code></pre><p>The developer writing the new code may not see that there already exists a variable called <code>isAllowed</code> and thinks they are initializing a new variable for their local scope. However, because of hoisting, the <code>isAllowed</code> inside the <code>for</code> loop is modifying the exact same variable as the one on line 1.</p><p>With <code>let</code> and <code>const</code> this is not possible because there's no hoisting and because they cannot be redeclared.</p><h4 id="functions-1">Functions</h4><p>Another thing that gets hoisted is function definitions that use <code>function</code> keyword:</p><pre><code class="language-javascript">hello("World"); // prints Hello World
function hello(target) {
console.log(`Hello ${target}`);
}</code></pre><p>This works because all function definitions (that use <code>function</code> keyword) are hoisted so they are available anywhere. This is actually really nice because it allows you to define functions at the end of the file and write the main logic on top.</p><p>However, I mentioned earlier that you can define functions with the arrow syntax as well:</p><pre><code class="language-javascript">hello("World");
var hello = target => console.log(`Hello ${target}`);</code></pre><p>Even when <code>hello</code> is declared with <code>var</code> keyword, the definition does not get hoisted, only the declaration so <code>hello</code> is undefined in line 1 and we get <code>TypeError: hello is not a function</code> if we try to run this.</p><h3 id="local-and-global-scope">Local and global scope</h3><p>Another exception to the rule is global scope. In our Functions section I mentioned that the way data is passed into a function is through its arguments. This is not entirely the only way to do it, however it's highly suggested to avoid global variables because it's harder to keep track of them and see where they are changed.</p><pre><code class="language-javascript">let number = 42;
function addToNumber(value) {
return value + number;
}
console.log(addToNumber(20)); //prints 62</code></pre><p>Any variable that's defined outside the functions is global and accessible anywhere after its initialization, including other blocks. In a small example like above, it's relatively easy to make sure <code>addToNumber</code> returns what we want but what if someone adds another function call before it:</p><pre><code class="language-javascript">let number = 42;
makeMagicHappen();
function addToNumber(value) {
return value + number;
}
console.log(addToNumber(20));</code></pre><p>Here, you don't know what <code>makeMagicHappen</code> or any function it is calling is doing to <code>number</code>. They can change it as they wish (it's global) so your function is now entirely dependent on every other piece of code that gets run before yours.</p>
codebase ep. 4: Web Components with Matias
2021-07-14T00:00:00Z
https://hamatti.org/posts/codebase-ep-4-web-components-with-matias/
<p><em><em><em><em><a href="https://hamatti.org/codebase">codebase</a> is a new developer community show on Youtube. It's a celebration of technology and different people working with different technologies. Each episode, I'm joined by a guest who knows something I don't. Together we chat about life and tech and build something live on stream.</em></em></em></em></p><p>To celebrate the sunny summer days, <a href="https://twitter.com/matsuuu_">Matias Huhta</a>, a software engineer at <a href="https://simplr.company/">Simplr Oy</a>, web component enthusiast, e-sports fanboy and an open source lover, joined me in <a href="https://www.youtube.com/watch?v=kd6XtCPEpsA">the fourth episode of codebase</a> to show what power lies in the realm of <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a>.</p><p>If you want to know when new episodes of codebase come out, <a href="https://www.youtube.com/channel/UCFTR0aya8Bhzf3WIT4VwLeA">subscribe to the Youtube channel</a>.</p><h2 id="what-are-web-components">What are Web Components?</h2><p>Matias described the idea of web components as <em>"an API that lets you create custom HTML tags which run custom code". </em>Web Components are <a href="https://caniuse.com/?search=web%20components">very well supported in modern browsers</a> and require close to zero tooling, which makes them very easy to get started with. No need for configuring webpack or other build and bundling tools.</p><p>Let's take a look at a example of custom counter button we started the live coding portion with. All the examples can be found at <a href="https://github.com/Matsuuu/Codebase/">Matsuuu/Codebase</a>.</p><pre><code class="language-javascript">class CounterButton extends HTMLButtonElement {
constructor() {
super();
this.count = 0;
const counterText = document.createElement("p");
counterText.innerHTML = `I've been clicked <span>${this.count}</span> times`;
const counterField = counterText.querySelector("span");
this.parentNode.insertBefore(counterText, this);
this.addEventListener("click", () => {
this.count++;
console.log(`I've been clicked ${this.count} times`);
counterField.innerText = this.count.toString();
});
}
}
customElements.define("counter-button", CounterButton, { extends: "button" });</code></pre><p>To build a custom web component, all you need is 1) a Javascript class that extends an existing element (in this case, <code>HTMLButtonElement</code>) or <code>HTMLElement</code> if you're not building on top of existing functionality, 2) a constructor that defines the functionality and 3) defining it in the <code>customElements</code> API.</p><p>The <code>CounterButton</code> in the above example can then be used just like a regular HTML tag in your html: </p><pre><code class="language-html"><html>
<body>
<button is="counter-button">Click me!</button>
<script type="module" src="./counter.js"></script>
</body>
</html></code></pre><h2 id="fast-hot-reloads">Fast hot reloads</h2><p>The feature that caught my attention during the episode was how fast the hot reloads were. To truly see that in action, watch the stream. Since there's no build step, using <code>npx @web/dev-server --watch --node-resolve</code> server provides immediate refresh on the browser when you change your code.</p><p>As I mentioned in <a href="https://hamatti.org/posts/codebase-ep-2-clojure-with-yka/">the blog post for second episode</a>, having a quick feedback loop is something I really enjoy. Minimizing the time it takes from changing the code to seeing the result increases my developer happiness and enables a faster iteration cycle.</p><h2 id="shadow-realm-err-dom">Shadow realm... err, DOM</h2><p>A concept called <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM">Shadow DOM</a> offers encapsulation to Web Components. It protects the components from the outside styling and protects the page using those components from having their styles spill over. </p><p>A simple example shows how CSS is affecting different DOMS:</p><figure class="kg-card kg-code-card"><pre><code class="language-html"><!DOCTYPE html>
<html lang="en">
<head>
<style>
p {
color: red;
font-size: 2em;
}
</style>
</head>
<body>
<p>Example in the normal DOM</p>
<shadow-example></shadow-example>
<script src="shadow-example.js"></script>
</body>
</html>
</code></pre><figcaption>index.html</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-javascript">class ShadowExample extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<p>This is a p tag inside the shadow DOM</p>
`;
}
}
customElements.define("shadow-example", ShadowExample);
</code></pre><figcaption>shadow-example.js</figcaption></figure><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/07/shadow-example.png" class="kg-image" alt="On top: large red text "Example in the normal DOM". On bottom: smaller black text "This is a p tag inside the shadow DOM"" /></figure><p>As you can see from the screenshot, the <code><p></code> tag we defined in the <code>shadowRoot</code> is not affected by the top level CSS.</p><h2 id="web-components-devtools">Web Components DevTools</h2><p>One thing we mentioned but didn't have time to go through in the episode was <a href="https://github.com/Matsuuu/web-component-devtools">Web Components DevTools</a> that Matias has been building and open sourced right after our live stream.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/07/webcomponents-tool.jpeg" class="kg-image" alt="A screenshot showing a web site with Web Components dev tools panel open" /><figcaption>Image from <a href="https://twitter.com/matsuuu_/status/1410178203914809345/">https://twitter.com/matsuuu_/status/1410178203914809345/</a></figcaption></figure><p>Having great tooling is crucial and now Web Component developers can get access to tooling right in the browser's dev tools while developing their components.</p><p>I highly recommend checking the tool out if you're working with Web Components.</p><h2 id="learn-more">Learn more</h2><p>Matias was kind enough to provide us some further resources if you got interested in Web Components.</p><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components | MDN</a></li><li><a href="https://open-wc.org/">Open Web Components</a></li><li><a href="https://modern-web.dev/">Modern Web - Guides, tools and libraries for modern web development</a></li><li><a href="https://lit.dev/">Lit - a library for building Web Components</a></li><li><a href="https://dev.to/thepassle/web-components-from-zero-to-hero-4n4m">Web Components: from zero to hero blog series</a></li></ul>
Learning Rust #6: Understanding ownership in Rust
2021-07-07T00:00:00Z
https://hamatti.org/posts/learning-rust-6-ownership/
<p>
<em>
Last December I finally started learning Rust and in January I built and
published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my monthly blog series that is defnitely
not a tutorial but rather a place for me to keep track of my learning and
write about things I've learned along the way.
</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>Learning Rust #6: Understanding ownership in Rust (you are here)</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<h2 id="learning-rust-talk-in-rust-denver-meetup">
<em>Learning Rust</em> talk in Rust Denver meetup
</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/06/learning-rust-denver-slide.png" class="kg-image" alt="A presentation slide with Ferris the Rustacean mascot and text "Learning Rust - experiences from a Python/Javascript developer"" />
</figure>
<p>
Before we jump into this month's topic, I wanted to let you know that I gave a
talk last month in Rust Denver meetup. The talk was about basically the same
things as this blog series: what I've learned in my ~7 months of Rust and
especially how those things reflect against my background as a mainly Python
and Javascript developer.
</p>
<p>
Go watch
<a href="https://www.youtube.com/watch?v=v4t4pWIxz8I&t=2s">the talk in my Youtube channel</a>
where I also host
<a href="https://hamatti.org/codebase">codebase livestreams</a> once a month
and then come back to finish reading this blog post!
</p>
<h2 id="garbage-collection-and-the-lack-of-it">
Garbage collection – and the lack of it
</h2>
<p>
I've been programming mostly in languages like Python and Javascript, both
which have automated
<a href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)">garbage collection</a>. What it means is that as a developer, I don't have to worry about whether a
variable should stay in memory or not, the compiler or interpreter takes care
of that.
</p>
<p>
And in very practical terms, what it means is that with Python or Javascript,
I don't have to think about <em>how</em> to refer to variables. It's always
just <code>name</code>. That is not the case with Rust.
</p>
<p>
My first touch to languages where I had to think about these things was a
Systems Programming course that I took a decade ago in the university. That
course focused on C and C++. Back then, I really struggled with gaining
routine on when to use the variable as-is, like <code>items</code> and when to
use a reference, like <code>&items</code>. Somehow, I ended up passing
that course but also didn't do anything more with C and C++ since.
</p>
<p>
I feel like I understand the theory and when I read about the topic, I keep
nodding. <em>Sure, this seems simple, of course it's like that. </em>And then
I get back to code and I get told by the compiler that I did not, in fact,
understand it.
</p>
<h2 id="stack-and-heap">Stack and Heap</h2>
<p>
To understand how memory management works in Rust, we need to take a look at
two concepts: the stack and the heap. I won't go into it in-depth here but if
you want to learn more, there's
<a href="https://doc.rust-lang.org/1.22.0/book/first-edition/the-stack-and-the-heap.html">a good explanation in the Rust book</a>
and
<a href="https://deepu.tech/memory-management-in-rust/">visualization in Technorage's blog</a>.
</p>
<p>
In a nutshell, Rust stores memory into its
<a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)">stack</a>
and its
<a href="https://en.wikipedia.org/wiki/Heap_(data_structure)">heap</a>. Stack
is very fast to allocate to and access from and it's the default place where
Rust stores your application's memory. Rust uses stack to store local
variables with limited scope.
</p>
<p>
Heap is used to store values that are passed between functions or have longer
lifetime than just the local scope. It's slower to access though which is why
Rust uses stack by default.
</p>
<h2 id="ownership-in-rust">Ownership in Rust</h2>
<blockquote>
<em>Ownership is Rust’s most unique feature, and it enables Rust to make memory
safety guarantees without needing a garbage collector.
<a href="https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html">- Rust Book</a></em>
</blockquote>
<p>
Rust is often praised for how it manages its memory through a system ownership
and how its borrow checker helps the developers to not make mistakes where
you'd be trying to access something that doesn't exist anymore.
</p>
<p>This first example comes from the Rust Book:</p>
<pre><code class="language-rust">fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}</code></pre>
<p>
If you try to run this code, it results in an error. That's because
<em>a move occured</em> and on line 5, we're trying to access a value that has
been moved on line 3.
</p>
<p>
When you do <code>let s2 = s1</code>, it does not copy the value nor the
reference of <code>s1</code>. Instead, it moves the reference and causes
<code>s1</code> no longer to point to anything so we cannot access it anymore
inside the <code>println</code> macro.
</p>
<p>
A move happens in assignment, passing into or returning from functions, when
assigned to a member in struct or when explicitly called with functions that
move ownership.
</p>
<p>
However, that's not always the case. In our above example, we used
<code>String</code> which gets moved on assignment but if a type implements
<code>Copy</code> trait, the value is copied, like with type <code>u32</code>:
</p>
<pre><code class="language-rust">fn main() {
let num: u32 = 150;
let num_copy = num;
println!("{:?} == {:?}", num, num_copy);
}</code></pre>
<p>
HashRust has
<a href="https://hashrust.com/blog/moves-copies-and-clones-in-rust/">a nice blog post on the topic</a>
and goes a bit deeper than I did here.
</p>
<p>
Understanding this has been the first step for me. For someone coming from
languages where this doesn't happen, it's been quite a challenge to
internalize.
</p>
<h2 id="borrow-checker">Borrow Checker</h2>
<p>
Rust comes with a borrow checker tool that helps you avoid these mistakes. It
<em>ensures that every access to memory is valid</em>.
</p>
<p>
Let's take a look at a bit more involved example (this one will result in
compiler error):
</p>
<pre><code class="language-rust">fn reverse(word: String) -> String {
word.chars().rev().collect::<String>()
}
fn main() {
let word = String::from("example");
let reversed = reverse(word);
println!("{:?} is reversed to {:?}", word, reversed);
}</code></pre>
<p>
Here we have a function <code>reverse</code> that accepts a
<code>String</code> and returns another <code>String</code>.
</p>
<p>
Inside our <code>main</code> function, we pass <code>word</code> into
<code>reverse</code> function which causes it to be moved and no longer
accessible after line 7. We have multiple ways to fix this.
</p>
<p>
First one is to accept a reference (<code>&String</code>) and pass such
reference (<code>&word</code>) into the function:
</p>
<pre><code class="language-rust">fn reverse(word: &String) -> String {
word.chars().rev().collect::<String>()
}
fn main() {
let word = String::from("example");
let reversed = reverse(&word);
println!("{:?} is reversed to {:?}", word, reversed);
}</code></pre>
<p>
Once we run this, we'll get output
<code>"example" is reversed to "elpmaxe"</code>. Sure, in Rust you could
simplify this even further by using <code>&str</code> but since that's
kinda a special case with strings, I wanted to keep this example more
generically applicable. If you wanna learn more about those, I recommend
checking out
<a href="https://tbuss.de/posts/2021/6-rust-slice-types/">Tomas Buß's blog post Rust Slice Types (and Strings), explained</a>.
</p>
<p>
Another way to fix the issue is to create a clone of the original variable.
Cloning is available for all types that implement
<a href="https://doc.rust-lang.org/std/clone/trait.Clone.html"><code>Clone</code> trait</a>. When cloning a variable, Rust creates a deep copy* so each of the variables
point to a different point in memory.
</p>
<p>
<em>* Kinda, at least. Developers are welcome to implement clone as they wish
but deep copy is often the default behaviour implemented.</em>
</p>
<pre><code class="language-rust">fn reverse(word: String) -> String {
word.chars().rev().collect::<String>()
}
fn main() {
let word = String::from("example");
let reversed = reverse(word.clone());
println!("{:?} is reversed to {:?}", word, reversed);
}</code></pre>
<p>
If you want to deep a bit deeper into understanding Borrow Checker system, I
recommend watching
<a href="https://www.youtube.com/watch?v=JfEWmQAACN8">Nell Shamrell-Harrington's talk The Rust Borrow Checker: a Deep Dive from
RustLab</a>. Tim McNamara also did
<a href="https://www.youtube.com/watch?v=ENF0V_T0Ayk">a nice video on the topic</a>
last week. And finally, LogRocket has
<a href="https://blog.logrocket.com/introducing-the-rust-borrow-checker/">a blog post written by Thomas Heartman called Understanding the Rust borrow
checker</a>
that has been very helpful for me.
</p>
<h2 id="what-s-so-hard-about-this-then">What's so hard about this then?</h2>
<p>
As I was reading more on the topic and writing this blog post, I once again
felt like I understand these concepts but when I actually write code, I still
struggle quite a bit with it.
</p>
<p>
I believe one key reason for that is that I don't yet have an internalized
mental model of thinking about these things since I've coded for a better part
of a decade without ever thinking about them in other languages. So when for
example I try to map over a vector of items, do operations on them and return
something, things start to feel very complicated. Things I'm very comfortable
doing in Python or Javascript suddenly become complicated in Rust.
</p>
<p>
I hope it's something that I'll get better at just by writing more Rust. As
I'm fixing the final bugs and issues in 235, I'm currently planning on what
project to pick up next to continue learning the language. I have a lot of
interest for static site generators and building one in Rust is something that
interests me for various reasons but I'm not sure if it forces me to use more
complex features of the language enough to really push my learning to the next
level.
</p>
My travel setup for Nintendo Switch
2021-06-30T00:00:00Z
https://hamatti.org/posts/my-travel-setup-for-nintendo-switch/
<p>It's summer time and often for me it means a bit of traveling. Obviously last summer and this summer, it's limited
due to the pandemic but I did have a change to visit my parents early this summer.</p>
<p>Nintendo Switch is a great travel console because it's a hand-held device. However, if you want to play it on the TV
or projector when you travel, you're gonna need more stuff and the Nintendo's own dock & charger combo is rather
bulky.</p>
<p>Here's my travel setup and how it has evolved over the two years I've had Switch. I don't always take everything with
me, only for longer trips.</p>
<h2 id="the-base-setup">The base setup</h2>
<figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/001.jpg" class="kg-image" alt="A Nintendo Switch next to a powerbank and a carrying case that's same size as Switch" /></figure>
<p>When I bought Switch as a birthday gift to myself (being adult is fun), I also bought this power bank that doubled my
mileage with the console. As a hand-held console, Switch has about 3-4 hours of power and I used to often sit in a
train for a whole day when traveling to Europe. It also doubles as a better kickstand for the console for tabletop
mode gaming and it allows me to charge my phone too.</p>
<p>That powerbank came with a case that fits the Switch, the powerbank, some games and small accessories like those
Joy-Con guards I've never used.</p>
<h2 id="then-the-gadgets-arrived">Then the gadgets arrived</h2>
<p>Little by little though, I started to expand my Switch's capabilities.</p>
<figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/002.jpg" class="kg-image" alt="A Nintendo Switch, a powerbank, a carrying case, a charger, HDMI cable, USB-C cable, small dock, capture card, bluetooth headset receiver and two 90 degree HDMI adapters" />
</figure>
<p>First, I got <a href="https://skullnco.com/collections/nintendo-switch/products/jumpgate-for-nintendo-switch">Skull
& Co's Jumpgate dock</a>. The dock itself is a bit larger but it has a detachable CORE which is basically a
Switch compatible (but 3rd party so bricking problem is always looming, do your research before buying) USB dongle
with HDMI and two USB-A ports. It plugs into the USB-C port in Switch (just like a regular dock) and boom, you're good
to go.</p>
<p>But with the dock, you also need a charger and a HDMI cable. I only ever use Nintendo's official charger but it's
bulky af. And HDMI cables ain't tiny either, especially since I usually want to have at least a good meter or two to
safely connect to TVs.</p>
<p>A bit after the dock, I also bought <a href="https://www.genkithings.com/products/shadowcast">Genki's Shadowcast</a>
from Kickstarter. Shadowcast is essentially a cheap and small capture card that plugs into HDMI port in the dock and
outputs to laptop through a USB-C cable. I don't use it for capturing that much but it does allow me to play games
from a computer screen – in my case my Macbook's 13" screen which is a big size improvement over Switch's own 6.2"
screen.</p>
<p>And since that capture card is bit bulky as well, I needed to get a 90 degree HDMI adapter so it fits to the CORE
dock at the same time as the power cable. And my purchase came with two of them which is nice if I ever need to
connect to a tight place on the other end.</p>
<p>To finish my setup, I also got Genki's Audio which is a bluetooth adapter for Nintendo Switch since despite having
built-in native bluetooth, the console doesn't allow connecting bluetooth headsets.</p>
<p>With all of these small gadgets and bulky additions, I was faced with a new problem. They don't fit into my small
cover case.</p>
<h2 id="gopro-to-the-rescue">GoPro to the rescue</h2>
<figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/003-1.jpg" class="kg-image" alt="A GoPro case with Nintendo Switch packed into it" /></figure>
<p>Luckily I also own a GoPro for which I bought this rather big case 'cause I figured I'd buy a bunch of additions for
it. I didn't.</p>
<p>The GoPro case fits my Switch with the powerbank, two Joy-Cons and all the adapters, dongles, chargers, cables and
docks in my setup.</p>
<p>An added bonus is that my GoPro and its accessories fit into the Switch case so I didn't even have to find a new home
for it.</p>
<h2 id="and-it-fits-my-backpack-perfectly">And it fits my backpack <em>perfectly</em></h2>
<figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/004.jpg" class="kg-image" alt="A sideview of a backpack with aforementioned GoPro case fitting in perfectly" /></figure>
<p>I've been a happy owner of Peak Design's Everyday Backpack for a while now and I was really happy to notice that in
my current arraignment, the GoPro case with my Switch inside fits perfectly to one of the slots. Sometimes all the
stars just align.</p>
<h2 id="any-recommendations-for-games">Any recommendations for games?</h2>
<p>You bet!</p>
<p>Firstly, I'm a major fan of Nintendo's classic series: Mario Odyssey is the best 3D Mario game (yeah, I said it,
Mario 64 diehards), Legend of Zelda Breath of the Wild is the best Zelda game (it even beats A Link to the Past) and
its sequel is coming next year! Link's Awakening remake is another great addition to the series and worth buying even
if you've played the original or the DX remake. Pokemon Sword & Shield is a great addition to the Pokemon series.
I haven't played Pokemon Let's Go games so can't comment on those but many people seem to love them.</p>
<p>And then to the other games. <a href="https://www.nintendo.com/games/detail/slay-the-spire-switch/">Slay the
Spire</a> is a deck-building rogue-like and one of my favorite games in the genre of <em>I can play for 5 minutes
when I wait for a bus</em>. Compared to the mouse-driven controls of its PC version, the Switch controls gets a bit
to get used to but I've already learned to love it.</p>
<p><a href="https://www.nintendo.com/games/detail/mario-plus-rabbids-kingdom-battle-switch/">Mario + Rabbids Kingdom
Battle</a> was a very positive surprise. I ignored it for a long time but for this summer's escape to my childhood
home for a few months, I wanted something completely new to play so decided to buy it and I've been enjoying (almost)
every bit of it (some of the puzzles are frustrating).</p>
<p><a href="https://www.nintendo.com/games/detail/shovel-knight-treasure-trove-switch/">Shovel Knight: Treasure
Trove</a> is one of the most hyped modern retro-style platformers and for a reason. It's a really good game and the
Treasure Trove version includes the four sequels/DLCs.</p>
<p>Something very different from others is <a href="https://www.nintendo.com/games/detail/good-job-switch/">Good
Job!</a>, an internship simulator where you need to complete tasks in an office in a chaotic setting where
everything breaks and it's perfectly fine - even encouraged. You can either aim to be super fast and cause mayhem or
avoid breaking things and take a more careful approach: both get awarded the same and offer very different approach to
the tasks and puzzles.</p>
<p>Mountain biking is cool and <a href="https://www.nintendo.com/games/detail/lonely-mountains-downhill-switch/">Lonely
Mountains: Downhill</a> captures the fun of the sport with beautiful graphics and offers a great zen-like calm
gameplay – or alternatively you can compete against yourself and try to find the best routes and optimizations to save
that one tenth of a second from your record. It also has a DLC or sequel that I haven't played yet but look forward
to.</p>
<p>Whether you liked Advance Wars games in the GBA era or haven't ever heard of them, <a href="https://www.nintendo.com/games/detail/wargroove-switch/">Wargroove</a> is worth checking out. It's a tactical
warfare game with cute characters and a lot of depth. The single player story is great and challenging and the
multiplayer gives unlimited amount of playability if you have a friend who likes the game as well. (and ps. <a href="https://twitter.com/NintendoAmerica/status/1404860468561317896">Advance Wars 1+2 remakes are coming to
Switch</a>.)</p>
<p>And probably the most niche recommendation: <a href="https://www.nintendo.com/games/detail/umurangi-generation-special-edition-switch/">Umurangi Generation</a>.
It's a photography game where you'll take photos of different things that you get tasked to take. I'm only in the
second level yet as I've only played it twice but so far it's been a fun game. You might have to be a bit of
photography enthusiast to enjoy the game though.</p>
<p>And if you like Metroidvania games, <a href="https://hamatti.org/gaming/hollow-knight-caught-me-in-its-nest/">Hollow
Knight</a> should be on your Switch as well.</p>
<p><strong>Update, August 2023:</strong></p>
<p> I have since picked up a few nice new games. <a href="https://www.nintendo.com/store/products/the-legend-of-zelda-tears-of-the-kingdom-switch/">The Legend of Zelda: Tears of Kingdom</a> is exactly as good or even better sequal to Breath of the Wild than I imagined and was totally worth the wait. I've spent so many hours roaming around Hyrule.</p>
<p>Another strong addition to my game collection is <a href="https://www.nintendo.com/store/products/tunic-switch/">TUNIC</a>, an isometric adventure game with a supremely cute fox protagonist.</p>
<p><a href="https://www.nintendo.com/store/products/advance-wars-1-plus-2-re-boot-camp-switch/">Advance Wars 1+2 Re-Boot Camp</a> finally came out in 2023 and while I would have hoped for a more "new adventures with the same engine", I've still been enjoying returning to Orange Star Nation on Switch.</p>
Why scheduling Slack messages and emails is so valuable for community builders
2021-06-23T00:00:00Z
https://hamatti.org/posts/why-scheduling-slack-messages-and-emails-is-so-valuable-for-community-builders/
<p>It was the first day of my holiday (editor's note: I was on a non-work Slack) last Saturday when I suddenly saw something in my Slack:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/slack-scheduled-messaging.png" class="kg-image" alt=""Write now, send later. You can now schedule messages to be sent at a later time, or another day altogether."" /></figure><p>Finally, native message scheduling had arrived to Slack and I have probably never been so happy about an update to a corporate tool – especially on a holiday.</p><p>But it's something I've been waiting for and dreaming about for years. It might sound a bit silly but let me tell you why this is such a big deal for someone like me who builds communities and organizes events.</p><h2 id="the-power-of-schedule">The Power of Schedule</h2><p>At first, it might sound a bit less human to rely on scheduled messages on instant messaging platforms and to certain extend I agree. I'll talk about this at the end of this post!</p><p>As a community builder, I run a lot of different programs and initiatives from events to newsletters and smaller "campaigns" – for the lack of better word. This means there's a significant delay between me having information and me sharing it with the community. Let me tell you a couple of examples:</p><h3 id="tech-weeklies-our-weekly-tech-meetup-at-work">Tech Weeklies – our weekly tech meetup at work</h3><p>In my current job, one of the things I run is <a href="https://futurice.com/techweeklies">Tech Weeklies</a>. It's a weekly tech meetup where my colleagues give talks to each other to share what they know. Occasionally we open the doors to the public, sometimes we invite people from the industry or partners to talk about their stuff and often we publish our talks in Youtube for everyone. It's also <a href="https://futurice.com/blog/tech-weeklies-as-a-learning-platform">a great platform for improving communication skills</a>.</p><p>For me as the organizer, it means that:</p><ul><li>On Monday, I reach out to that week's speakers and ask them to provide some info on their talks (1-2 messages depending on the amount of speakers)</li><li>On Wednesday, I send an email to our internal mailing list and tell everyone what's coming on Friday</li><li>On Wednesday, I also send the same message to our developer channel in Slack</li><li>On Friday, I create a thread for information and discussion few hours before the event</li></ul><p>That's 3-4 Slack messages and 1 email each week.</p><p>Now the actual time spent working on those info messages is probably 5 minutes a week. But when spread around the week, taking into account context switching, being in Slack to send them and so on, it multiplies really fast.</p><p>I've been scheduling those emails already from the very beginning but I couldn't do the same with Slack natively.</p><p>Given the power to natively schedule these Slack messages, I can do everything at once.</p><h3 id="building-communities-outside-of-work">Building communities outside of work</h3><p>I also build and run a bunch of communities outside of work. What it usually means is that I don't always have <em>availability </em>to work on them when they would be best worked on.</p><p>For our meetups at <a href="http://turkufrontend.fi/">Turku <3 Frontend</a>, I tend to work 1-2 days at the end of summer to prep for the fall and 1-2 days in the beginning of January to prep for the spring. But I can't send all the information at the end of that day: it won't reach people, they forget or it's just an information overload.</p><p>Being able to write all these messages at once, schedule them (and if needed, modify if things change) is gonna be such a time saver and improve the communication.</p><h3 id="i-have-info-much-earlier-than-i-share-it">I have info much earlier than I share it</h3><p>As I create different things for the community, my work is often done in two major spikes: one at the beginning and another at the end. On a single day, I might be planning and preparing things for 4-5 projects/events and like in my previous example, I can't send all of that info at once. Hence, scheduling is super valuable.</p><p>This year I knew somewhere in February or March that I wanted to crowdsource my colleagues blog posts for our July edition of <a href="https://hello.futurice.com/dev-breakfast">Dev Breakfast newsletter</a> but it wasn't the right time to ask for them. I had my form for collecting those ready weeks before I actually asked for them.</p><p>If I could have scheduled that message (with reminders!) on one sitting, it would have opened up so much mental energy and time for other stuff. Instead, I had to wait for the specific Monday to arrive and wait for the right time and then be there to send the message.</p><p>I also tend to do most of my administrative and preparative work on Mondays and focus on people and relationships Tuesday through Friday. This new feature enables this habit even more.</p><h3 id="being-more-appreciative-for-other-people-s-schedule">Being more appreciative for other people's schedule</h3><p>This one is a bit more controversial in a sense. I'm a strong believer that Slack and email are asynchronous tools where you should be able to send a message to someone even during the night or during their holiday. But since that's not the culture in every work place, I try to be more mindful about this.</p><p>So if I know my colleague takes Wednesdays off and I get a great idea on Wednesday, I can now schedule that message to be sent on Thursday – instead of having to add myself a reminder to start the discussion on a later time.</p><h2 id="more-time-for-human-connection">More time for human connection</h2><p>So earlier I mentioned about how it might feel a bit less human to use these scheduling features and if done wrong, it absolutely becomes that.</p><p>The key here is this: <strong>scheduling information emails opens up my time for actual discussion with people in my communities.</strong> Since I don't have to make sure I'm at Slack sending a message at a given time, I can use that time to focus on people – including taking better care of myself.</p><p>I've earlier written about <a href="https://hamatti.org/posts/event-organizer-automate-what-you-can-focus-on-people-with-all-youve-got/">why event organizers should automate as much as possible</a> and scheduling things is essentially automation. It's about categorizing work so you can do a lot of information sharing at once and distribute the delivery of those messages to best suit the receivers' schedules and habits.</p><p>Some Slack app bots have already enabled this type of functionality before but my experience with working with people is that those bots tend to get ignored quite quickly compared to a real human sending the message.</p><p>So for my colleagues and members of the community: you don't have to worry about me becoming just a scheduling machine. I'll just automate the boring bits so we can focus on things that truly matter.</p>
My (career) story in tech and developer relations
2021-06-16T00:00:00Z
https://hamatti.org/posts/my-career-story-in-tech-and-developer-relations/
<p>All of our careers are different. When zoomed out enough, they start to blend and look similar but when you really look carefully, all of them are unique. I wanted to share my story on my blog as an example of how I ended up where I am and hopefully give inspiration to someone who doesn't see themselves in a <em>"I didn't know anything about coding a year ago and now I make $200k at Facebook" </em>story that you occasionally see in social media.</p><p>That is not to say that there's anything wrong with those paths either. But I do want to provide another example of way slower progression because when I look at the stories that gain most popularity in social media, it makes me feel inadequate because it took me a decade to learn programming and years to become employed in developer relations.</p><h2 id="in-the-early-1990s-">In the early 1990s…</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/05/me-and-my-brother-91.jpeg" class="kg-image" alt="A picture from 1991, I'm standing next to my brother who's playing on a computer." /><figcaption>A picture from 1991, I'm standing next to my brother who's playing on a computer.</figcaption></figure><p>My first touch to computers happened very early on, in the turn of the 1990s. Our first computer was a 286 computer with MS-DOS and Windows 3.0. It still sits in my childhood home's bedroom but hasn't been booted up in decades.</p><p>I spent my childhood with this computer. I would play games like Supaplex and Bubble Bobble, learn how to operate the computer on a command line, use MS Paint and write in a text editor. I remember one time when my sister was reading a book out loud to me and I would type it to the computer. That was how I learned to write, even though I didn't understand all the words yet.</p><p>At some point, my sister had made a picture of some horses with MS Paint. The picture was larger than what fit into the GoldStar monitor of ours (seen in the picture above) and I vividly remember being so curious (and bit annoyed) when my sister would scroll to show the right-most horse in the picture (and then hide it from me again). I don't know how much of that memory is true and how much has changed over the years but I credit most of my early curiosity towards computers on that moment. I'm pretty sure that picture is on a floppy disk in one of my boxes at home.</p><p>I have a lot to thank to my parents for this early access to computers. I was 2 years old when the first computer came to our house and my parents who were not computer users themselves, gave me pretty much free reign with the computer as long as I was able to use it as they figured out computers might be a thing in the future.</p><p>Years went buy and I kept growing up. My fascination towards computers never faded. In the late 90s, we upgraded the computer to a Pentium 3 computer that ran Windows 98. At the same time, we subscribed to a local computer magazine that opened my eyes to so many things about technology.</p><p>As the new millenia started, I began to experiment with installing Linux distributions and copying small programs from the magazines to run. My first application was a number guessing game built with Delphi – copied character for character from the magazine.</p><h2 id="then-i-discovered-the-web">Then I discovered the web</h2><p>Somewhere in the early 2000s (around 2002), I started to learn programming with PHP and HTML. As we switched to ADSL internet and I wasn't limited to 15-30 minutes of Internet per day, I spent a lot of my free time reading Finnish programming forums like Mureakuha and Ohjelmointiputka which were kinda the early, local equivalent to Stack Overflow of today.</p><p>Little by little, I started to learn. I was building small websites for myself and my hobbies. The combination of my interests lined up really well: ever since being a kid, I was interested in sports and especially sports statistics and those were a very good thing to build software around.</p><p>In early 2000s, the access to information and mentoring was not like it is today, especially for someone who didn't come from a family of money and who lived in a small town. We did subscribe to a monthly magazine on personal computing but only rarely was there something in it about programming.</p><p>Around the age of 15, I ran a local sports club with my friends and organized tournaments and obviously those tournaments needed a website to showcase the results. I ended up spending 15 years building websites and software for that hobby all the way from local to international level. And I learned lot while doing it.</p><p>It was great to have a sandbox of sorts where I could build something other people were actually using but I didn't have anyone telling me what to do. So I could experiment a lot and occasionally break things a lot too.</p><h2 id="studying-programming">Studying programming</h2><p>During high school, I did a double degree program combining the Finnish <em>lukio/ylioppilastutkinto </em>and <em>ammattikoulu/datanomi </em>education. Also during my high school, I enrolled to a special program that gave me access to a minor in computer science in the local university.</p><p>That's when I started to learn a bit more about programming in a more theoretical and organized manner. I learned how to build more complex things to websites and got my first touch to object-oriented programming with Java in the university program.</p><p>For years and years, my main struggle was that I understood how to build small pieces of functionality like functions/methods but I had no idea how to put those things together to build fully functioning software. It had gotten me surprisingly far when building websites with PHP but it bothered me a lot that I just couldn't figure out how to put things together.</p><p>Probably around my first year at the university, I finally started to get a better grasp at these ideas. On my second year, I was already tutoring first-year students and doing teaching assistant jobs at the university, thanks to my interest in helping others learn. I also started doing a bit of freelance software development on smaller projects during my second year – most of them being about cleaning data and transforming it into formats that students of other fields could use in their thesis analyses.</p><p>Studying at the university really sped up my learning and freelancing provided a bit of practical application of those skills which helped me develop a more comprehensive picture of software development.</p><h2 id="moving-to-silicon-valley">Moving to Silicon Valley</h2><p>Then suddenly, things started to move fast. After my third year at university, I worked on a larger freelance project that helped me build a great portfolio project. I also learned from a friend about a program called <a href="https://startuplifers.org/">Startup Life (now Startuplifers)</a> that helped local students gain internships in startups in Silicon Valley.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/sharks-kings.jpeg" class="kg-image" alt="Me posing for the camera with San Jose Sharks hockey game on the background" /></figure><p>It took me months of preparing, lots of failed interviews and lots of disappointments before I ended up in an interview with Dave & Justin from <a href="https://chartio.com/">Chartio</a> and to my delight, they offered me an internship. I moved to San Francisco in 2014 and got to work with a really brilliant team and got to learn from some of the best software engineers I've ever met.</p><p>But I was so scared to start there. I didn't have a lot of experience – actually it was my first real job in the industry – but thanks to great mentoring especially from Justin, the CTO, and Paul, my colleague, I started to learn faster and faster and ended up building a few things I'm really proud of.</p><p>And not only was the experience of working in a fast-growing startup very educational, it was a very big year of personal growth. It was the first time I lived abroad all alone, with nobody I knew beforehand within thousands of miles. I got to know a lot of amazing people who are still my friends all these years later.</p><p>When I returned home after my visa ran out, I joined another startup, <a href="https://www.smartly.io/">Smartly.io</a> which I had a great, yet short experience with. I ended up leaving after a few months due to not finding the ad tech industry something I wanted to work with. At Smartly, I got to continue my learning journey that had started at Chartio in a very similar environment.</p><h2 id="community-management">Community Management</h2><p>And then I did something a bit strange – I kinda left tech, or at least software development. After leaving Smartly, I got a job as a community manager in a non-profit startup community Boost Turku. At Boost, I ended up working for two years, organizing dozens of events and running two batches of a successful startup accelerator program Startup Journey.</p><p>My time at Boost was one of the most fulfilling times of my life. Working in a non-profit really fit my values and the ability to build a vibrant community of enthusiastic young people around it was really exciting. And I've been happy to help out the community also as an alumni coaching startups and hosting workshops.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/06/in-tallinn.jpeg" class="kg-image" alt="Me with a microphone, asking a question" /><figcaption>Photo by Andres Raudjalg</figcaption></figure><p>But I also didn't quite tech completely. I figured I might not end up liking community management after all, so I decided to stay up to date on all things tech and founded Turku <3 Frontend, a frontend developer community that grew bigger than I had ever imagined. I can't wait for us to be able to host regular meetups again. Through that I made so many amazing friends and got my first touch into building developer communities – and learned I'm pretty good at that. Much better, than I was as a software developer and much more at home with that.</p><p>As years went on, I realized that I could maybe do that for a living. I started to dream about speaking in a tech conference (did my first one in Finland at PyCon Finland 2016 and first international at PyCon CZ in 2019).</p><p>In 2018, I joined my current company at <a href="https://futurice.com/">Futurice</a> and little by little over the first year, built a case for developer advocate role, pitched that to the leadership and in 2019, started as a full-time developer advocate that I have now enjoyed over the past two years.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/helsinkijs-jan-2020.jpeg" class="kg-image" alt="A group of people sitting in the audience, watching me give a talk in an office" /></figure><p>During those years, I've launched new initiatives like <a href="https://hello.futurice.com/dev-breakfast">Dev Breakfast newsletter</a>, worked on existing ones like <a href="http://futurice.com/techweeklies">Tech Weeklies</a> and Global Code Camp but also organized, hosted and spoken in a big number of meetups and conferences. I've also worked with local developer communities and meetups to help them prosper and helped my great colleagues to get into public speaking, from sharing to collegues all the way to the conference stages.</p><p>In addition to work stuff, my hobbies have been around developer communities as well. I love teaching programming and I've been active in multiple communities like <a href="http://railsgirls.com/">Rails Girls</a>, <a href="https://codebar.io/">codebar</a> and Boost Summer of Programming, helping new people get excited about technology. </p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/06/hki-dev-lunch-jan-2020.jpeg" class="kg-image" alt="A group of people eating lunch on a long table" /></figure><p>I also blog frequently in this blog and occasionally write articles and blog posts to other places as well. All with the hopes of inspiring people to get into tech and helping them to learn the basics.</p><p>In 2019, I ended up doing more than 52 talks, podcast visits or workshops and it was one of the best years in my life. Then in 2020, everything slowed down due to the pandemic and I can't wait for it all to be over and get back to hanging out with people.</p><h2 id="slow-curve">Slow curve</h2><p>It probably took you few minutes to read the above and it might feel like I've progressed quite fast and gotten a lot of amazing opportunities. The latter is absolutely true and I've been lucky to have so many incredible opportunities both at work and on my free time. </p><p>But I'd argue, it hasn't been fast. At least it doesn't feel like it. I've been programming for some 20 years and I'm no developer superstar. I can get around a codebase and build things but looking at other people in the industry who build amazing things, it makes me feel less talented. That's also one major reason why I decided to focus on helping those people do great things and help new people reach those levels.</p><p>In terms of developer relations, my strengths are definitely in building engaging communities, teaching programming, helping people build cool things and sharing those stories. Often I feel like the industry is looking for people who are core contributors in popular open source projects and software development superstars.</p><p>One thing I do have to say though: I enjoy programming now more than I have ever done. As I don't code for a living, I get to put my energy on my free time to building <a href="https://hamatti.org/software">tools and small projects</a> for fun for myself and other people to use. And I get to learn new languages like I've been learning Rust recently. And I get to combine that with blogging and doing talks while meeting fantastic developers around the world.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/06/001-pyconcz-fridayhug.jpg" class="kg-image" alt="Me taking a selfie from PyCon CZ stage and everyone in audience spreading their arms to hug" /><figcaption>A Friday Hug from PyCon CZ stage in summer of 2019, my first international tech conference</figcaption></figure><p>I've also been eager to experiment with new ways to build communities and build things that developers are interested in. Most recently this spring I started monthly livecoding series on Youtube called <a href="https://hamatti.org/codebase">codebase</a>. I'm not super comfortable with creating things on video but in a world where everyone is now sitting at home, I'm experimenting with it. I want to keep providing interesting opportunities to learn and explore new ideas to my communities and since I'm active in multiple different tech communities, I'd like for the people in those to meet each other and learn from each other.</p><h2 id="final-words">Final words</h2><p>I've been so privileged that I've been able to experience all these things that I have. First turning my hobby of software development into a career that took me to Silicon Valley and back and then turning my other hobby of community building into a career as well that has taken me all around Europe.</p><p>That's why my mind is constantly in this very confused dual state where at one side, I understand how amazing things I've been able to do in such a short time. But on the other side, it feels like my career progression has been so much slower than all the stories I see on social media.</p><p>What I really want to say with this blog post is this: <strong>we all advance on our pace</strong>. So much seems to be down to luck: who you meet and when, what opportunities come up, what job openings are open when you're looking and so on and so on. I've said no to many opportunities that took me weeks to make decision to say no to – and every now and then I think what could have happened.</p><p>Especially the way this pandemic slowed things down to a halt in many aspects, it hasn't been easy to cope with mentally.</p>
codebase ep. 3: PHP with Larry
2021-06-09T00:00:00Z
https://hamatti.org/posts/codebase-ep-3-php-with-larry/
<p><em><em><em><em><a href="https://hamatti.org/codebase">codebase</a> is a new developer community show on Youtube. It's a celebration of technology and different people working with different technologies. Each episode, I'm joined by a guest who knows something I don't. Together we chat about life and tech</em></em>nology.</em></em></p><p>In <a href="https://www.youtube.com/watch?v=qxwLdvdl04s">the third episode of the codebase</a>, aired live on May 25th, I had the pleasure to discuss the colorful history and current state (and a bit of the future) of PHP with <a href="https://twitter.com/crell">Larry Garfield</a>. He is a staff engineer at TYPO3 and has been working with PHP for over 20 years, has written a couple of books on the topic (<a href="https://leanpub.com/thinking-functionally-in-php">Thinking Functionally in PHP</a> and <a href="https://leanpub.com/exploringphp80">Exploring PHP 8.0</a>). His fascinating background with PHP turned our discussion into not only a great history lesson but also insights from how the languge has evolved and why many people have a very outdated view of PHP based on the state the language and the ecosystem was in early 2000s.</p><p>If you want to know when new episodes of codebase come out, <a href="https://www.youtube.com/channel/UCFTR0aya8Bhzf3WIT4VwLeA">subscribe to the Youtube channel</a>.</p><h2 id="from-php-4-to-php-8-0-and-beyond">From PHP 4 to PHP 8.0 and beyond</h2><p>In the turn of the millenia and early 2000s, PHP was a language that was so easily accessible to many developers that it took a huge part of the market share for building backend code. Pretty much every web host allowed for PHP so it ended up being used by a lot of people – me included. PHP was my first serious programming language and I ended up building so many sites and small web apps with it.</p><p>One interesting story Larry told during the discussion was about the <a href="http://www.gophp5.org/home.html">GoPHP5 project</a> which was a collaboration between multiple PHP framework communities to push for requiring PHP5 so that web hosts would have incentive and need to upgrade their servers to PHP5.</p><p>We also had a nice short discussion about PHP's package manager <a href="https://getcomposer.org/">Composer</a> and what it does right and how it's hold up its place in the PHP ecosystem.</p><p>Finally, we discussed the current state of PHP with PHP 8.0 with some live code examples and even saw a glimpse of the future as Larry demoed upcoming Enums feature that he's been contributing to and which is coming in PHP 8.1.</p><h2 id="how-to-get-started-with-php">How to get started with PHP?</h2><p>Larry recommended two good starting points for learning modern PHP:</p><p><strong><a href="https://phptherightway.com/">PHP the Right Way</a> </strong>is a great resource for learning about the basics and finding references to many of the core features of the language.</p><p><strong><a href="https://www.php.net/manual/en/index.php">PHP Manual</a> </strong>is in my opinion one of the best technical documentations I've ever used. One thing that sets it apart from many other resources is the combination of authored documentation and community use case examples.</p><h2 id="codebase">Codebase</h2><p>If you missed our PHP discussion, you can watch it <a href="https://www.youtube.com/watch?v=qxwLdvdl04s">on Youtube</a>. I also recommend checking out <a href="https://hamatti.org/codebase">our previous episodes</a> in which we talked about accessibility using HTML and CSS and backend development with Clojure.</p><p>I'll see you next time!</p>
Learning Rust #5: Rustlings
2021-06-02T00:00:00Z
https://hamatti.org/posts/learning-rust-5-rustlings/
<p>
<em>Last December I finally started learning Rust and in January I built and
published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my monthly blog series that is defnitely
not a tutorial but rather a place for me to keep track of my learning and
write about things I've learned along the way.
</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types
</a>
</li>
<li>Learning Rust #5: Rustlings (you are here)</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
This month I wanna talk about one particular project that is designed to help
people learn Rust. It's called
<a href="https://github.com/rust-lang/rustlings">rustlings</a> (lovely name
btw) and it's a collection of exercises that walk you through basics of
writing Rust.
</p>
<p>
The basic premise of rustlings is simple: there's a bunch of Rust files that
don't compile. The design of the whole is beautiful: it will try to compile
the exercises one by one in order and stops when the compiler fails. Now it's
your job as the learner to fix the file so it compiles and save it. Once it
compiles, you're introduced to the next exercise.
</p>
<p>
It's not exactly teaching you. There are hints you can activate if you're
stuck but otherwise it's up to you to figure out how to fix the compiler's
error messages and get the application to run. But it's a great companion for
learning from documentation, tutorials or a book.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2021/04/rustlings-screenshot.png" class="kg-image" alt="A screenshot of the rustlings app output with few successfull exercise prompts and a compiler error" />
</figure>
<p>
I'm especially excited about the approach it takes. Not only does
compiler-error driven development a great way to organize the tasks, it
teaches you one of the key skills: debugging your app from errors.
</p>
<p>
As I've been teaching programming for years, the skill of reading those errors
is often not talked about enough and causes many students to ignore them in
the beginning. Instead of reading the error message and using its information
to debug what's wrong, many beginners just go back to their code and try to
figure it out. (I even wrote
<a href="https://hamatti.org/guides/humane-guide-to-python-errors/">a humane guide to Python errors</a>
a while back to help beginners learn them.)
</p>
<p>
I learned about the project about 4 months into my learning journey and at
that point, I felt comfortable and confident about the exercises. I think I
would have benefited more had I started working on them earlier. Regardless,
they provided a great way to make sure I understand the basics and provided an
additional angle.
</p>
<p>
I've seen some other languages have projects that have failing tests that you
need to fix (like <a href="http://clojurekoans.com/">Clojure Koans</a>). This
is the first one where I've seen the project being based on fixing errors and
I'd hope to see more of them. I know from experience that creating good
examples for these kind of exercises is super difficult so my hats off to the
people who've built rustlings so people like myself can learn.
</p>
My Employer Supports My Open Source Contributions (via honeypot.io)
2021-05-31T00:00:00Z
https://hamatti.org/posts/external-cult-how-my-employer-supports-open-source/
<p>Read full article at <a href="https://cult.honeypot.io/reads/my-employer-supports-my-open-source-contributions/">cult.honeypot.io</a>.</p>
Does it make sense to finish what you started?
2021-05-26T00:00:00Z
https://hamatti.org/posts/does-it-make-sense-to-finish-what-you-started/
<p>I'm a university dropout – three times actually. It's not a brag or something that defines me but it's something that comes up in the discussions quite often. There are two main reasons for why I never finished my studies:</p><ol><li>As many other computer science majors, I found myself in the working life as a developer during my studies</li><li>I ended up prioritizing things I found more worthy than writing a thesis </li></ol><p>Let's talk about these two.</p><p>First of all, this blog post is not a <em>degree</em> vs <em>no degree </em>discussion. There's definitely value in finishing your studies and in most fields, it's necessary. I happen to live in Finland and work in tech, a combo that means I can get quite far without a degree. </p><p>This discussion is about an argument I hear over and over again when discussing this topic with people: <em>should you finish what you started? </em>And especially the common case of adding <em>for the sake of it </em>at the end of that sentence. </p><h2 id="a-tale-of-trade-offs">A tale of trade-offs</h2><p>The argument usually goes like this: "A thesis is a good way to show you can stick to what you started and finish it, even if it is not that valuable itself." And I absolutely agree with that: it definitely shows commitment and ability to push through something you might not enjoy doing, kudos for you.</p><p>However, that's only one half of the coin. And the other half, you'll only find out if you ask the person you're talking with because there are so many different reasons.</p><p>For me, it was a clear question of priorities. At the time I last tried to write my thesis, I was working full time at a non-profit startup community hosting workshops and running startup accelerator programs. I was also teaching programming, organizing meetups and other events and doing what I consider more impactful and meaningful than writing a thesis that only my thesis supervisor (and maybe my mom) would read.</p><p>I made a concious decision that I wanted to spend my days and evenings doing something that mattered to people. Many of those people who participated in those workshops are now either entrepreneurs or software developers – or both. That's an impact my thesis would have never gotten. And it's time that would have had to be cut if I wanted to finish my thesis.</p><p>Most of the time, the decision we make on how we use our time is a trade-off. We choose to do one over the other and I was lucky to be in a situation where my other option was much better.</p><h2 id="but-is-it-even-a-good-idea-to-commit-and-finish">But is it even a good idea to commit and finish?</h2><p>After I tell the above to someone, it's quite common to hear "yeah but your case is different, <em>usually</em>...". Which I'd say is a cop out. There's something even more valuable to discuss though. I would argue, that the idea that it's (nearly) always a virtue to finish what you started, is a flawed one. Not only does it cause people spend years studying something they realize in their first year is not their thing or working on job they hate – because they started it and quitting is bad.</p><p>If you're a business owner or employer and you've hired someone who then starts doing a long project (either on their own or because you told them to) and it turns out 20% into the project that it's not gonna be worth it. Maybe there's something else they could do that's more valuable to the business or maybe what they are doing is harmful.</p><p><strong>Would you still like them to "stick to what they started and show they can finish it"? </strong></p><p>I don't find it valuable to continue doing something if there's a better option, just because you started it and need to show you can do it. Time is a very limited resource and wasting it doing something nobody gets anything out of, is just plain stupid.</p><h2 id="i-experiment-a-lot">I experiment a lot</h2><p>I've always been the guy who starts a lot of different projects and initiatives. As I was growing up, I got told about it a lot because I didn't finish most of them and I still don't.</p><p>Luckily, in adult life it's called <strong>experimentation</strong> and is actually very valuable. Sometimes my project dies after I buy the domain, sometimes I run it for a few months only to realize it's not what I want to spend time on. And then there are the gems. The things I run for 5, 10, 15 years. Because they are the ones worth doing.</p><p>And if I had stuck with the first thing I tried, I would have never discovered those things. I ran a local sport thing for 15 years, my proudest creation <a href="http://turkufrontend.fi/">Turku ❤️ Frontend</a> turned 5 last December. <a href="https://hello.futurice.com/dev-breakfast">A newsletter at work</a> that I created and operate is gonna hit its 2nd anniversary in the summer. And my newest creation, <a href="https://hamatti.org/codebase">codebase</a> has been running for 2 months already.</p><p>But yeah, I never wrote that thesis so I guess that shows I lack commitment. Or maybe you can ask any of those people who became developers after learning programming if I made the right call. </p>
Scheduling operations in Python
2021-05-19T00:00:00Z
https://hamatti.org/posts/scheduling-operations-in-python/
<p>I was recently helping out a friend build a small project that would run on regular intervals to fetch data from an API that was provided by an IoT device. In the Linux world, <a href="https://en.wikipedia.org/wiki/Cron">cron</a> is a popular job scheduler but its syntax isn't the most user-friendly and I've always had a bit of problems with it.</p><p>In this project, I wanted to look into other options, preferably something from the Python ecosystem. Over 10 years ago, Adam Wiggins wrote an article <a href="https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/">Rethinking Cron</a> which is a really nice article and highlights many of the issues I've also had with cron:</p><blockquote>Cron problems are difficult to debug. The arcane syntax of crontab is terse to the point of near inscrutability, making it easy to accidentally schedule jobs at the wrong time. And the subtle differences between a cronjob’s shell environment and your command prompt’s shell environment can be maddening. Lack of feedback makes these or any other problem with your cronjobs difficult to diagnose.</blockquote><p>After doing some research, I ran into <a href="https://pypi.org/project/schedule/">schedule module</a> which works on Python 3.6 onwards and also references Wiggins' article as inspiration so I decided to test it out.</p><h2 id="how-to-schedule-with-schedule">How to schedule with schedule</h2><p>After installing the package with <code>pip install schedule</code> and importing it with <code>import schedule</code>, it provides an easy to read and understand API:</p><figure class="kg-card kg-code-card"><pre><code class="language-python">import schedule
def job():
print("I'm working...")
schedule.every().hour.do(job)
while True:
schedule.run_pending()
time.sleep(1)</code></pre><figcaption>(example simplified from <a href="https://schedule.readthedocs.io/en/stable/">documentation</a>)</figcaption></figure><p>I would argue that anyone reading that has a pretty good idea what it's gonna do even if you have never seen the <code>schedule</code> package. Especially if we compare that to the equivalent cronjob:</p><pre><code class="language-cron">0 * * * * python job.py</code></pre><p>If you're dealing with cronjobs, I highly recommend <a href="https://crontab.guru/">crontab.guru</a> to help debug and build new schedules.</p><p>I publish my blog every Wednesday morning (I have a manual process so I do it when I wake up). If I had my blog publish process as a Python function, I could do</p><pre><code class="language-python">schedule.every().wednesday.at("08:00").do(publish_blog_post)</code></pre><p>The <a href="https://schedule.readthedocs.io/en/stable/examples.html">examples page</a> in the documentation gives a good overview of the API it exposes. While it reads well, one thing I don't particularly like about it is the fact that <code>every().hour</code> and <code>every(5).hours</code> use different attribute (singular vs plural) instead of <code>hour</code> always being in one form.</p><figure class="kg-card kg-code-card"><pre><code class="language-python">@property
def hour(self):
if self.interval != 1:
raise IntervalError("Use hours instead of hour")
return self.hours</code></pre><figcaption>Implementation of hour in schedule</figcaption></figure><p>You even get an error if you try to use <code>every(5).hour</code> instead of <code>every(5).hours</code>. To my personal taste, it's giving a bit too much emphasis on the natural language over technical implementation. It's still a nice library that does its job. </p><p>For a library like this, that you don't use and develop with every day, I think readability is a key asset and with that, <code>schedule</code> shines.</p>
codebase ep. 2: Clojure with Ykä
2021-05-12T00:00:00Z
https://hamatti.org/posts/codebase-ep-2-clojure-with-yka/
<p><em><em><a href="https://hamatti.org/codebase">codebase</a> is a new developer community show on Youtube. It's a celebration of technology and different people working with different technologies. Each episode, I'm joined by a guest who knows something I don't. Together we chat about life and tech and build something live on stream.</em></em></p><p>In <a href="https://www.youtube.com/watch?v=7q6udGF6a28">the second episode of codebase</a>, aired live on April 27th, I sat down with <a href="https://twitter.com/ykarikos">Yrjö Kari-Koskinen</a> – or Ykä as I know him – because I knew of his interest towards Clojure. In addition to being a Clojure developer, Ykä is also a host of <a href="https://koodiapinnanalla.fi/">Finnish backend technology podcast Koodia pinnan alla</a> which I am a big fan of, so it was super nice to sit down with him to talk about life and technology.</p><p>If you want to know when new episodes of codebase come out, <a href="https://www.youtube.com/channel/UCFTR0aya8Bhzf3WIT4VwLeA">subscribe to the Youtube channel</a>. </p><h2 id="what-is-clojure">What is Clojure?</h2><p><a href="https://clojure.org/">Clojure</a> is a dialect of <a href="https://en.wikipedia.org/wiki/Lisp_(programming_language)">Lisp</a> developed by Rich Hickey, first published in 2007. It's a predominantly functional programming language that runs on top of Java Virtual Machine (JVM). Its Java connection offers access to the wide Java ecosystem directly from Clojure itself.</p><pre><code class="language-clojure">(println "Hello codebase!")</code></pre><p>In addition to Clojure, there's also <a href="https://clojurescript.org/">ClojureScript</a> that compiles to Javascript, allowing developers to build frontend web applications with the language and gain some benefits from a full-stack Clojure approach. </p><p>A great place to start learning Clojure is its official guide <a href="https://clojure.org/guides/learn/syntax">Learn Clojure</a>.</p><h2 id="read-eval-print-loop-workflow">Read-eval-print-loop workflow</h2><p>One of the neat workflows of working with Clojure that we got to see in our live coding session is how Clojure is being developed with a very tight feedback loop using a REPL built into the editor.</p><p>I've always been a big fan of REPLs myself: with both Python and Javascript, a big part of my workflow is piecing together a working piece of code in an interactive session and then transferring that into the code files.</p><p>With Clojure, it felt much more tightly knit into the workflow though. <a href="https://youtu.be/7q6udGF6a28?t=2294">In the stream</a> (around 38:15 onwards), you can see an example of how Ykä was building the data flow from external API to what our functions needed to return by working in the REPL, verifying the code and data was working as expected and then transferring the code into the source code files.</p><h2 id="building-a-bit-of-web-backend">Building a bit of web backend</h2><p>In the livestream, Ykä showed how to build a bit of web backend that fetches data from one source (in this case, 3rd party API) and translates that into the data we want to send to our frontend. You can find the example code that was used from <a href="https://github.com/ykarikos/hello-codebase">ykarikos/hello-codebase</a>.</p><p>In this example, we used <a href="https://www.metaweather.com/">MetaWeather's API</a> to find temperatures for a given city for a time period and calculated the average temperature that would then be sent to the caller of our backend API.</p><p>The example is bit contrived since we only have a limited amount of time to live code in the streams but it provides a really good starting point in case you want to start experimenting and learning Clojure with something that's more real-life suitable than just a todo app.</p><h2 id="get-involved-with-the-clojure-communities">Get involved with the Clojure communities</h2><p>If you're interested in Clojure or are already working with it, you should definitely join the Clojure communities near you. There are <a href="https://clojure.org/community/resources">global Clojure community discussions in for example at Slack, Zulip and IRC</a>. </p><p>Here in Finland, there's also <a href="https://clojurefinland.github.io/">a very active community</a> that organizes meetups and <a href="https://clojutre.org/2020/">ClojuTRE</a> conference and <a href="https://clojurebridge.org/">ClojureBridge</a> workshops. Local Clojurians can also been seen in <a href="https://koodiklinikka.fi/">Koodiklinikka's</a> Slack's #clojure channel and in #clojure-finland channel in <a href="http://clojurians.net/">the global Clojurians Slack</a>.</p><p>By searching for "clojure [your city]" in your favorite search engine, you can find your local Clojure community. I personally find developer communities great places to spend time and get to know fellow developers who share the same interests as you do. And they are great for the moments when you get stuck or have trouble understanding something.</p>
Learning Rust #4: Parsing JSON with strong types
2021-05-05T00:00:00Z
https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/
<p>
<em>Last December I finally started learning Rust and in January I built and
published my first app with Rust:
<a href="https://github.com/Hamatti/nhl-235">235</a>. Learning Rust is my
new monthly blog series that is defnitely not a tutorial but rather a place
for me to keep track of my learning and write about things I've learned
along the way.</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>Learning Rust #4: Parsing JSON with strong types (you are here)</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
One big difference with Rust compared to Javascript and Python that I've been
writing for most of my career is strong static typing. With JS and Python, I'd
just call an API, get some data in JSON, parse that into a Python dict or
Javascript object and then access different values directly.
</p>
<p>
With Rust, I'm working with differently typed structures and while I
understand the value of it, sometimes it feels very cumbersome to write.
</p>
<h2 id="i-started-with-serde_json-value">I started with serde_json::Value</h2>
<p>
When I started working with Rust to build 235, I decided to use
<a href="https://serde.rs/">Serde</a> that seemed to be a popular library for
seriealizing and deserializing data. While I was still learning the basics, it
felt overwhelming to figure out the correct way of defining types for the
entire API response. I decided to leave that as a refactoring exercise for a
later day and started by using
<code><a href="https://docs.serde.rs/serde_json/value/enum.Value.html">serde_json::Value</a></code>, which is kinda a catch-all solution. It is an Enum that represents any
valid JSON value.
</p>
<pre><code class="language-rust">#[tokio::main]
async fn fetch_games() -> Result<serde_json::Value, Error> {
let request_url = String::from("https://nhl-score-api.herokuapp.com/api/scores/latest");
let response = reqwest::get(&request_url).await?;
let scores: serde_json::Value = response.json().await?;
Ok(scores)
}</code></pre>
<p>
Here's my original <code>fetch_games</code> function that called the API and
parsed the data into a <code>serde_json::Value</code>. Nice and clean, easy to
use.
</p>
<p>But it led to my code being full of code like this</p>
<pre><code class="language-rust">let games = scores["games"].as_array().unwrap();
let home_team = &game_json["teams"]["home"]["abbreviation"].as_str().unwrap();
let all_goals = game_json["goals"].as_array().unwrap_or(&empty);</code></pre>
<p>
I had to code very defensively because any type conversion with
<code>as_</code> methods could have fail and thus needed to be unwrapped and
in some cases, made sure the keys even existed in the beginning:
</p>
<pre><code class="language-rust">if (&game_json["teams"]).is_null() {
return None;
}
let home_team = &game_json["teams"]["home"]["abbreviation"].as_str().unwrap();
let away_team = &game_json["teams"]["away"]["abbreviation"].as_str().unwrap();</code></pre>
<p>
Using <code>serde_json::Value</code> did help me build the first functioning
versions of my application but in the back of my head I had the knowledge that
defining the structures as Rust structs would make my code better.
</p>
<p>
One key thing that kept me from doing that in the beginning was I couldn't
figure out how to deal with dynamic keys. The API I use uses team
abbreviations as keys on certain places and I had no idea how to do it so I
left the issue for months.
</p>
<h2 id="how-to-define-structs-for-json">How to define structs for JSON</h2>
<p>
Using custom structs to define expected types with JSON is rather
straight-forward if you're familiar with structs.
</p>
<p>
I'll start with the example
<a href="https://docs.serde.rs/serde_json/">from the documentation</a>:
</p>
<pre><code class="language-rust">#[derive(Serialize, Deserialize)]
struct Person {
name: String,
age: u8,
phones: Vec<String>,
}</code></pre>
<p>
Here we define a <code>Person</code> that has name as <code>String</code>, an
age as <code>u8</code> and a list of phone numbers as
<code>Vec<String></code>. Once we have this definition, all we have to
do is to use that as a type of the result and <code>serde_json</code> and Rust
will validate that the structure and types of data matches what we defined:
</p>
<pre><code class="language-rust">// Parse the string of data into a Person object.
let p: Person = serde_json::from_str(data)?;
// Do things just like with any other Rust data structure.
println!("Please call {} at the number {}", p.name, p.phones[0]);</code></pre>
<h2 id="then-we-draw-the-rest-of-the-owl">Then we draw the rest of the owl</h2>
<p>
I found the base example very good and nice to follow. However, that wasn't
all that I needed so next I needed to figure out how to
<a href="https://knowyourmeme.com/memes/how-to-draw-an-owl">draw the rest of the owl</a>.
</p>
<h3 id="snake_case-of-rust-with-camelcase-of-json">
snake_case of Rust with camelCase of JSON
</h3>
<p>
First, Rust likes to use
<a href="https://en.wikipedia.org/wiki/Snake_case">snake_case</a> and gives
you a warning by default if you don't. JSON keys are usually in
<a href="https://en.wikipedia.org/wiki/Camel_case">camelCase</a> so my editor
and my shell were full of warnings and that was annoying. I learned that I can
silence them by defining an <code>allow</code> attribute for each struct that
needed it:
</p>
<pre><code class="language-rust">#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct TeamResponse {
pub abbreviation: String,
pub id: u64,
pub locationName: String,
pub shortName: String,
pub teamName: String,
}</code></pre>
<p>
I haven't yet found out if there's a way to use snake case'd names and
automatically map them to corresponding JSON values. (Raymond Hettinger from
the Python community has
<a href="https://www.youtube.com/watch?v=wf-BqAjZb8M">a nice talk for why it's valuable to have your code in idiomatic Python</a>; much of it is applicable to any language).
</p>
<p>
edit. 2021-05-06: Thanks to
<a href="https://github.com/Hamatti/nhl-235/pull/27#discussion_r626688198">PatatasDelPapa's comment in pull request</a>, I was able to fix this:
</p>
<pre><code class="language-rust">#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamResponse {
pub abbreviation: String,
pub id: u64,
pub location_name: String,
pub short_name: String,
pub team_name: String,
}</code></pre>
<h3 id="code-organizing-api-response-types-into-a-separate-file">
Code organizing API response types into a separate file
</h3>
<p>
Second, you'll see that in the snippet above, definitions start with
<code>pub</code>. That's because I wanted to move my API response type
definitions to its own file so they don't pollute my main.rs and it's easier
to find and change them. So I made <code>api_types.rs</code> file, moved them
all there, added plenty of <code>pub</code>s and imported them with
</p>
<pre><code class="language-rust">mod api_types;
use api_types::{APIResponse, GameResponse, GoalResponse};</code></pre>
<p>in main.rs.</p>
<h3 id="dynamic-keys">Dynamic keys</h3>
<p>
Third, I needed some support for dynamic keys. I was expecting to have to jump
through some hoops but this ended up being one of the easiest steps in the
process. Thanks to
<a href="https://github.com/serde-rs/serde/issues/1387">this GitHub issue</a>,
I found the solution: use
<code>HashMap<String, serde_json::Value></code> for any object that has
dynamic keys.
</p>
<pre><code class="language-rust">#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct GameResponse {
pub status: StatusResponse,
pub startTime: String,
pub goals: Vec<GoalResponse>,
pub scores: HashMap<String, serde_json::Value>,
pub teams: TeamsResponse,
pub preGameStats: PreGameStatsResponse,
pub currentStats: CurrentStatsResponse,
}</code></pre>
<p>In the documentation of the API, <code>scores</code> field is defined as</p>
<blockquote>
<code>scores</code> object: each team’s goal count, plus one of these possible
fields:<br />- <code>overtime</code>: set to <code>true</code> if the game
ended in overtime, absent if it didn’t<br />- <code>shootout</code>: set to
<code>true</code> if the game ended in shootout, absent if it didn’t
</blockquote>
<p>and looks like</p>
<pre><code class="language-json">"scores": {
"BOS": 4,
"CHI": 3,
"overtime": true
}</code></pre>
<p>
The keys for each team are gonna be dynamic so by using <code>HashMap</code>.
The downside of this that I wasn't able to figure out, is that I'd like to put
<code>overtime</code> and <code>shootout</code> keys into the type
definitions.
</p>
<h3 id="optional-keys">Optional keys</h3>
<p>
And finally, fourth, some keys are optional – or to be specific in this case,
they are dependent on the situation.
</p>
<p>
I'm happy I already learned about <code>Option</code> types earlier (<a href="https://hamatti.org/posts/learning-rust-2-option-result/">check out Learning Rust #2 if you haven't heard about them</a>). And that's all you need with Rust and <code>serde_json</code>:
</p>
<pre><code class="language-rust">#[derive(Debug, Serialize, Deserialize)]
pub struct APIResponse {
pub date: DateResponse,
pub games: Vec<GameResponse>,
pub errors: Option<HashMap<String, serde_json::Value>>,
}</code></pre>
<p>
In this example, which is the top level definition of my API response type,
I've defined <code>errors</code> to be optional – it's only present if the API
I use found some errors in the data it's using.
</p>
<p>
By saying that the type of the data is <code>Option</code>, we'll either get a
<code>Some(value)</code> if the key exists and <code>None</code> if it
doesn't. For most cases in this application, it's not random which keys are
there and which are not. So if I'm already in a branch of logic where the
prerequisite is being dealt with, I can trust that the value exists and can
unwrap without dealing with potential <code>None</code>s.
</p>
<h2 id="result-much-cleaner-code">Result? Much cleaner code</h2>
<p>
Refactoring the code with these definitions is not only an exercise of writing
code for the sake of writing code. The result of this work is that we push the
problems at the boundaries of parsing the data and we don't have to deal with
them at the logic level anymore.
</p>
<p>
I already showed some examples in the beginning about the code I wasn't happy
about. Let's see how they look like now:
</p>
<p><strong>Before</strong></p>
<pre><code class="language-rust">if (&game_json["teams"]).is_null() {
return None;
}
let home_team = &game_json["teams"]["home"]["abbreviation"].as_str().unwrap();
let away_team = &game_json["teams"]["away"]["abbreviation"].as_str().unwrap();</code></pre>
<p><strong>After</strong></p>
<pre><code class="language-rust">let home_team = &game_json.teams.home.abbreviation;
let away_team = &game_json.teams.away.abbreviation;</code></pre>
<p><strong>Before</strong></p>
<pre><code class="language-rust">let empty: Vec<serde_json::Value> = Vec::new();
let all_goals = game_json["goals"].as_array().unwrap_or(&empty);</code></pre>
<p><strong>After</strong></p>
<pre><code class="language-rust">let all_goals = &game_json.goals;</code></pre>
<p>
I don't know about you but I'm very happy to see my code being much more
simpler and easier to read. I can also trust that once the data comes through
from the API, it won't have any surprises.
</p>
<h2 id="anything-i-don-t-like">Anything I don't like?</h2>
<p>
Writing type definitions for these kind of nested structures feels sometimes
overly complicated. There are some definitions that I don't need anywhere
outside one value in the structure and having to write a named struct to that
feels bit annoying.
</p>
<p>
One thing I did enjoy in Typescript is a way to write in-line nested types and
interfaces like this:
</p>
<pre><code class="language-typescript">interface IEndpoints {
auth: {
login: string;
}
}</code></pre>
<p>In Rust, I would have needed to make two structs.</p>
<h2 id="pull-requests-for-all-these-changes">
Pull requests for all these changes
</h2>
<p>
If you wanna check out all the changes and how the codebase cleaned up during
this process, you can find it in GitHub:
<a href="https://github.com/Hamatti/nhl-235/pull/27/">hamatti/nhl-235/pull/27</a>.
</p>
Falling forward
2021-04-28T00:00:00Z
https://hamatti.org/posts/falling-forward/
<blockquote><em>This isn't flying, this is falling with style!</em></blockquote>
<p>
It's been over 25 years since Buzz Lightyear and Woody were arguing about the
semantics of <em>flying</em> vs <em>falling with style </em>(Toy Story, 1995).
</p>
<p>
This post is about my personal and professional life, things I've learned and
things I might still learn one day. It's not a technical post and it's most
definitely not any sort of guidance for how to live your life, that's up to
you. But hopefully it gives you some insights into how I see the world and
where it has taken me.
</p>
<p>
When I was a kid in the 90s, I grew up as a rather stereotypical straight A
student. I studied hard for two reasons: first was my curiosity towards
learning but back then equally strong (if not even stronger) motivation was to
avoid failing. In the Finnish school system grading of 4–10, anything below 9
was a catastrophe for me.
</p>
<p>
A funny sidenote: my mom likes to tell a story of a time when I came home from
school (I was maybe 9 or 10) crying because I had gotten a 10- while someone
else scored full marks. That's how important it was for me as a kid.
</p>
<p>
And I'd be lying if those high expectations for myself would have waded away
over the years. But something else definitely has. Little by little in my
early to mid 20s, I adopted what people would call <em>a growth mindset</em>.
</p>
<p>
A big factor in that personal growth was the local startup community (huge
shoutout to <a href="https://boostturku.com/">Boost Turku</a> and
<a href="https://www.aaltoes.com/">Aaltoes</a>) and few individual people
there. People who believed in me more than I did and pushed me forward to try
out things I wasn't certain I'd be great at. Ville, Mikko, Elina – you know
who you are – thanks!
</p>
<h2 id="i-still-hate-failing">I still hate failing</h2>
<p>Failing to do something still sucks. Every single time.</p>
<p>
But over the past 6-7 years, I've learned to kinda embrace it. These days, I
start a lot of new projects (both on my own time and at work) and experiment a
lot to see what sticks: what is fun to do, what the community enjoys and I'm
very eager to quit them as soon as I see they don't work.
</p>
<p>
I've learned to embrace my curiosity and excitement for things and ignoring
the voices (both my inner critic but also the haters) that keep telling me
that "finishing what you start is a virtue". Sometimes it is but being too
stubborn to keep doing something that isn't worth it is just silly.
</p>
<p>
I'm a university dropout because I felt that organizing workshops, teaching
programming and helping the local community was more important and valuable
than writing a thesis that nobody would read. Surely, it would have been nice
to graduate and I probably could have done it if I had prioritized differently
but I don't value the degree or finishing something for the sake of finishing
that much.
</p>
<p>
I have a bunch of domains that have a "something's coming" landing page or a
redirect to my personal site, so many git repositories of projects I started
and stopped working on (for now) and I don't worry about them anymore. If the
time is right, I'll get back to them and finish or if I lose interest, I'll
drop them all together.
</p>
<h2 id="experimentation-leads-to-success">Experimentation leads to success</h2>
<p>
I'm not especially smart or innovative guy. What I am (and proud of it) is a
combination of being stubborn enough to hate failure and willing to experiment
with dozens and dozens of ideas before sticking to them. That's how all of my
successful projects have been born and that's why they have been so
successful.
</p>
<p>
I've been keeping up with momentum of many projects so that even when I fall
with them, I keep stumbling forward to next projects.
</p>
<p>
<a href="http://turkufrontend.fi/">Turku ❤️ Frontend</a> turns 6 years old this
fall, I ran sports organization(s) for 15 years and have been teaching
programming for the past 8 so once I find things that work, I stick to them.
Currently I'm also in the early phases of Helsinki Dev Lunch (since Aug 2019)
and <a href="https://hamatti.org/codebase">codebase</a> (since Mar 2021) as
well as
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">learning Rust</a>
(since Dec 2020) and running a developer newsletter
<a href="https://hello.futurice.com/dev-breakfast">Dev Breakfast</a> (since
Aug 2018). And bunch of other stuff that may or may not become something.
</p>
<p>
As a Developer Advocate, I'm a strong believer in experimentation. Each
community is different and unique so you need to do different things with
them. Some groups might enjoy and work well in a chat-like environment like
Discord or Slack. Others might shine in knowledge bases and forums. The third
group can be a local meetup or conference (or lunch group) community. Many can
be some combination of these. Building those communities, we need to be
constantly experimenting and listening to the community to learn what works
and where people would shine.
<a href="https://hamatti.org/posts/build-your-community-like-a-garden/">I wrote about an analogy of community building and gardening.</a>
</p>
<h2 id="a-lot-of-self-reflection-in-the-pandemic">
A lot of self-reflection in the pandemic
</h2>
<p>
This past year has been horrible to say the least. And one thing it has forced
me to do a lot is reflecting on my life, work and habits and thinking about
what makes me happy. And it's been rough not being able to do those things.
</p>
<p>
So I'm writing a series of these blog posts (like
<a href="https://hamatti.org/posts/learning-in-public/">Learning in Public</a>
and this) to put some of my thoughts into words for future, post-pandemic me
to read.
</p>
Documentation-driven command line tools in Python with docopt
2021-04-21T00:00:00Z
https://hamatti.org/posts/documentation-driven-command-line-tools-in-python-with-docopt/
<p>A month ago I wrote <a href="https://hamatti.org/posts/how-to-parse-command-line-arguments-in-python/">a blog post</a> to document examples for how to use <a href="https://docs.python.org/3/library/argparse.html">argparse</a> to parse command line arguments in Python. After publishing that, I got a lot of suggestions for different libraries that allow you to parse arguments in different ways. I decided to take a look at some of them, starting today with <a href="https://github.com/docopt/docopt">docopt</a>.</p><p><strong><em>🚧 A quick note about project state.</em></strong><em> The original docopt project has not been updated in years. A fork of the project called </em><a href="https://github.com/jazzband/docopt-ng"><em>docopt-ng</em></a><em> was created to keep it updated (and is a drop-in replacement) and that has been maintained further. However, according to </em><a href="https://jazzband.co/projects/docopt-ng"><em>Jazzband</em></a><em>, this project is at risk of losing maintenance as well. For what they do, I think they are still usable but for longevity and for building more complex projects, it's good to keep that in mind when considering your options.</em></p><p>Docopt claims to be a <em>Pythonic command line arguments parser, that will make you smile. </em>It works by reading & parsing the docstring of the application and provides a dictionary with parsed values. One nice benefit from this approach is that it allows/forces (depending on your viewpoint) you to develop your CLI tool documentation-first. I have experimented with <em>documentation-driven development </em>a couple of times. The idea there is that you first write the documentation of your application so you'll think about the interface and use cases first and then program your app to match that. So I'm quite excited about the idea.</p><figure class="kg-card kg-code-card"><pre><code class="language-python">"""Naval Fate.
Usage:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py ship shoot <x> <y>
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
naval_fate.py --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Naval Fate 2.0')
print(arguments)</code></pre><figcaption>example from <a href="https://github.com/docopt/docopt">https://github.com/docopt/docopt</a></figcaption></figure><p>Here's an example from the documentation. It starts with a docstring that is both the specification of your application's arguments <strong>and</strong> the help message when running the application with <code>python naval_fate.py --help</code>. </p><p>You can also provide your own help message as a parameter to the function but for most use cases, writing it as the docstring seems the best option as then it's also easy to find and modify when developing the application further.</p><p>On the first glance, I like it. I like when the logic/configuration/specification is written in a way that is easy to read and understand. That's where docopt shines. It's easy for me to read through that and see what arguments it accepts.</p><p>There are also <a href="https://github.com/docopt/">ports of docopt to other languages</a>.</p>
How Spice Program supported my creation of 235 (via Futurice.com)
2021-04-15T00:00:00Z
https://hamatti.org/posts/external-how-spice-supported-235/
<p>Read full article at <a href="https://futurice.com/blog/how-spice-program-supported-my-creation-of-235">futurice.com</a>.</p>
Introducing developer community show codebase
2021-04-14T00:00:00Z
https://hamatti.org/posts/introducing-developer-community-show-codebase/
<p><em>codebase is a new developer community show on Youtube. It's a celebration of technology and different people working with different technologies. Each episode, I'm joined by a guest who knows something I don't. Together we chat about life and tech and build something live on stream.</em></p><p>My goal for codebase is to be a nice and casual gathering of developers. Especially during these socially distanced times, I miss just hanging out with people to talk about tech and work on small projects and learn something new. It's not a replacement for conference talks but rather the hallway and dinner discussions that I've always loved in events. Less scripted, less structured and more just a discussion of interesting things.</p><p>The show is aimed for all developers regardless of your skill level or the technologies you use. If you're a Javascript or Python developer, I highly recommend joining future shows where talk about completely different languages like Clojure or Haskell.</p><p>Software development is essentially problem solving. Not in a puzzle-solving type of way but in a way of mapping the reality into code and building workflows that make sense for the user and are enjoyable to use. Your selection of tools (languages, libraries, etc) will heavily affect the way you think about and approach problems.</p><p>American linguistic Benjamin Lee Whorf talked about how our language shapes our reality – or at least the perception of it. While <a href="https://en.wikipedia.org/wiki/Linguistic_relativity">Sapir-Whorf hypothesis</a> is not scientifically proven nor accepted, in my experience, something similar affects our software development too in a way. If you use object-oriented programming languages for 20 years, you're probably more likely to start intuitively map the domain you're working in as objects and their methods and same happens with functional languages and their approach.</p><p>So even if you're not going to switch your main language of choice to another, I highly recommend learning a bit of different languages because it forces you to approach problems from different angles and with different toolsets so you don't allow your thinking to take the easiest route. This in turn can help you solve problems in different ways even when you use your trusty tools.</p><p>As a Python developer, you might not get the most out of the eventually upcoming Python episode. But you might learn something new and valuable about Clojure or Haskell episodes – and vice versa.</p><p>The first episode of codebase was broadcasted live on March 23rd and you can find it below. The <a href="https://www.youtube.com/watch?v=7q6udGF6a28">next episode will air live on April 27th at 17:00 EEST</a> and I'm joined by <a href="https://twitter.com/ykarikos">Yrjö Kari-Koskinen</a>, a software developer from Tomorrow Tech and a host of <a href="https://koodiapinnanalla.fi/">Koodia pinnan alla podcast</a>. We'll be talking about Clojure and building a bit of web backend with it.</p><h2 id="episode-1-let-s-build-accessible-forms-with-fotis">Episode 1: Let's build accessible forms with Fotis</h2><p>In the very first episode of <a href="https://hamatti.org/codebase">codebase</a>, I was joined by <a href="https://fotis.xyz/">Fotis Papadogeorgopoulos</a>, a software developer and accessibility specialist from <a href="https://futurice.com/">Futurice</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2021/04/codebase-ep1-sm.png" class="kg-image" /><figcaption><a href="https://www.youtube.com/watch?v=vUArlJgzQ9M">Watch the episode on Youtube</a></figcaption></figure><p>With Fotis, we decided to talk about accessibility and picked HTML forms as an example case to test out this new codebase format. In this episode, we talked about accessibility in general, semantic HTML in the context of forms, the importance of using labels with your inputs, why focus styles help users know where they are in the form and how to use CSS to style your forms.</p><p>Check out <a href="https://www.youtube.com/watch?v=vUArlJgzQ9M">the first episode on Youtube</a> and let me know what you liked about it in the Youtube comments or <a href="https://twitter.com/hamatti">in Twitter</a>. Also, if you have ideas for what languages, technologies or libraries you'd like to see me talk about with the guests, let me know that too!</p><p><em>We are currently working on providing closed captioning for the recording of the first episode.</em></p><h2 id="further-reading-on-web-accessibility">Further reading on web accessibility</h2><p>Fotis was kind enough to also provide some extra materials so you can continue learning accessibility related topics. All the descriptions after the links are from the site itself and not from either of us.</p><ul><li><a href="https://www.a11yproject.com/">The A11Y Project</a><br /><em>The A11Y Project is a community-driven effort to make digital accessibility easier.</em></li><li><a href="https://adrianroselli.com/2017/05/under-engineered-custom-radio-buttons-and-checkboxen.html">Under-Engineered Custom Radio Buttons and Checkboxen by Adrian Roselli</a><br /><em>I keep seeing overly-complex controls with additional elements as style hooks, scripting to make up for non-semantic replacements, images that need to be downloaded, and so on.<br /><br />This is silly. Here are some really simple styles to make radio buttons and checkboxes look unlike native controls (which seems to be the main goal from these over-engineered attempts).</em></li><li><a href="https://24ways.org/2018/inclusive-considerations-when-restyling-form-controls/">Inclusive Considerations When Restyling Form Controls by Scott O'Hara</a><br /><em>When striving for custom styled controls, one must be careful not to forget about the inherent functionality and accessibility that many provide. People expect and deserve the products and services they use and <strong>pay for</strong> to work for them. If these services are visually pleasing, but only function for those who fit the handful of personas they’ve been designed for, then we’ve potentially deprived many people the experiences they deserve.</em></li><li><a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">Accessible Icon Buttons by Sara Soueidan</a><br /><em>There is a handful of ways that an icon button can be implemented accessibly. This article is an overview of them all.</em></li><li><a href="https://tatianamac.com/posts/prefers-reduced-motion/">prefers-reduced-motion: Taking a no-motion-first approach to animations by Tatiana Mac</a><br /><em>Animations help to breathe life into interactive experiences. Animations, especially when overused and abused, can make people very ill. Through this article, I hope to provide you an approach and guidance to discussing how you/your company use animation thoughtfully and responsibly.</em></li><li><a href="https://shop.smashingmagazine.com/products/form-design-patterns-by-adam-silver">Form Design Patterns by Adam Silver (a book)</a><br /><em>Without forms, the web is a passive experience where content is just consumed. But with forms the web can be collaborative, creative and productive. Forms are at the center of every meaningful interaction, so they’re worth getting a firm handle on.</em></li></ul>
Learning Rust #3: crates.io & publishing your package
2021-04-07T00:00:00Z
https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package/
<p>
<em><em><em><em>Last December I finally started learning Rust and </em></em></em>in January<em><em><em>
I built and published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my new monthly blog series that is
defnitely not a tutorial but rather a place for me to keep track of my
learning and write about things I've learned along the way.</em></em></em></em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result</a>
</li>
<li>
Learning Rust #3: crates.io & publishing your package
<em><em>(you are here)</em></em>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
This week I wanted to take a look at the publishing and distribution of Rust
applications. It presents a major difference to what I'm used to and what I
love in the web app world so I'll discuss that first and then look at the
practicalities and the documentation of getting your application to the
computers of your users.
</p>
<h2 id="when-i-was-young-we-bought-software-from-store">
When I was young, we bought software from store
</h2>
<p>
I want to start this with a bit of a story. When we got our first family
computer in the early 1990s, we'd buy software in floppy disks from a computer
store. This was true a decade later with the small change that the software
was distributed in CDs.
</p>
<p>
The Internet was young and the connections and download speeds were not
suitable for mass scale distribution of software as downloadables. In the
early 2000s, I started to use some online services like forums and early
versions of social media via browser.
</p>
<p>
When I was 15, I knew I couldn't write my own software, get it to store
shelves and have people buy it. I was just a beginner developer learning the
first steps. So building things on the web, to be used with browsers was what
I decided to go with. It probably wasn't as concious and thoughout decision as
I see it through the goggles of nostalgy. However, the fact that I could build
something, upload it (we used to deploy by moving PHP files with FTP client to
the server) and it would available to anyone interested in using it was
definitely a major reason for why I fell in love with the web as a medium.
</p>
<p>
So, for 15 years, almost everything I built, was deployed to a webserver
somewhere and accessed via browser. I did build some tools for myself that
were run locally but they were never meant to be given to other people so
distribution was not a factor.
</p>
<p>
As the web development as a field progressed over the 15 years, distribution
became even easier and instead of ftp'ing bunch of files to the server, we
started to set up automatic deploys and working with version control. But the
essence of distribution didn't change: I make a change and it's available to
everyone without needing any action from them.
</p>
<h2 id="then-i-built-my-first-rust-binaries">
Then I built my first Rust binaries
</h2>
<p>
This brings us back to 2021 and me learning Rust. When the end result of your
work is a binary executable, everything in terms of distribution changes. No
longer, can I fix an issue quickly and have it in front of every user within
minutes.
</p>
<p>
Thanks to the internet and Rust's package ecosystem, there's no need to burn
the app on a CD, put into a beautiful cardboard box and sell in stores. Now
people can download it in seconds, update it easily and get things rolling
with the new version.
</p>
<p>
But there's still one major difference: people need to update their app
manually. There might be some ways to let people know that their application
needs updating: first thing that comes to mind is making an API call to a
server that returns the last version number and if it's newer than what the
user has, print a "you should update" message. Thinking of it now, I might end
up doing that before 235 hits version 1.0.
<em><a href="https://twitter.com/hamatti">Let me know</a> if there's a better
way.</em>
</p>
<p>
But still, people may run your application for months or years without
checking for updates, especially if it works. Or they might abandon it quickly
if there are annoying bugs and when they notice it, there ain't no new version
yet.
</p>
<p>
The authority of updates moves from the developer to the user. And that
changes so much.
</p>
<h2 id="crates-io">crates.io</h2>
<p>
Rust's package registry is called <a href="https://crates.io/">crates.io</a>.
If you're a Javascript developer, it's similar to npm. For Python users, it's
similar to PyPi. It's the place where you find the tools you need to write
great Rust programs without reinventing the wheel. It's also where you can
install 235 from.
</p>
<p>
<code>cargo install nhl-235</code> is the command line command that pulls the
package and builds it.
</p>
<p>
To publish Rust code in crates, you need two thing: account in crates.io and
<code>Cargo.toml</code> file with
<a href="https://doc.rust-lang.org/cargo/reference/publishing.html">certain information you can check from the docs</a>. After those are correctly done, <code>cargo publish</code> transfers your
code via the highway of information to the package registry so others can
install it and use it.
</p>
<p>
I really enjoy the process. It's developer experience is fantastic and once
I've gotten a new release version done, I just publish it and people can
update it with <code>cargo install nhl-235</code>. So good.
</p>
<h2 id="building-binaries">Building binaries</h2>
<p>
You don't have to share your program in crates.io though. Since the result of
the build process is a binary, you can just share that. I'm still learning how
to use the
<a href="https://github.com/Hamatti/nhl-235/releases/tag/v0.2.5">Releases page of GitHub</a>
but whenever I publish to crates.io, I upload a new release build to GitHub to
which I also link from
<a href="https://hamatti.github.io/nhl-235/">my website</a>.
</p>
<p>
This allows people to download and run your program without any need for the
Rust language tooling installed.
</p>
<p>
I develop 235 on a macos and I'm not 100% sure if my builds work on any
machine. It's on my todo list to learn more and test it more thoroughly but so
far, nobody has complained. If you wanna give me a hand and use Windows or
Linux, download
<a href="https://github.com/Hamatti/nhl-235/releases/tag/v0.2.5">the latest release from here</a>
and run it from the command line and
<a href="https://twitter.com/hamatti">let me know in Twitter</a> if it works
or not.
</p>
<p>
A huge benefit of the binaries compared to crates.io is that the end user
doesn't need any Rust tools installed and I gain full control over
distribution. And those updates? It's bit more of a hassle since people need
to go and download the binaries again and replace the old ones.
</p>
<p>
Eventually 235 will reach design and technical maturity where it won't be
updated with new features and hopefully won't have breaking bugs anymore. At
that point, the need to update constantly will thankfully disappear. Until I
may have to do some changes to the API.
</p>
<h2 id="making-a-release-is-more-deliberate">
Making a release is more deliberate
</h2>
<p>
One thing I've noticed is that when I can't just deploy a change or fix in a
matter of minutes, I need to be much more deliberate, test things out more and
figure out if the design of the program is correct. In the beginning, it's
more okay to experiment especially as I'm learning the language but as I reach
version 1.0, I'm looking to only make changes in larger chunks.
</p>
<p>
All in all, publishing 235 via crates.io and as binaries has been a very
educational process and a refreshing change of pace compared to my usual web
development projects.
</p>
<p></p>
My pockets are full of games
2021-03-31T00:00:00Z
https://hamatti.org/posts/my-pockets-are-full-of-games/
<p>It's time to talk about board games again. I love them. What I don't love is the big boxes many of they come in and how hard those games are to take with me in the backpack. Being a creative problem-solver, over the past couple of years I've experimented with a lot of things to solve this problem.</p><p>I have built so many things that <em>fit into my pocket</em> that my pockets are getting quite full. I wanted to take a brief look at what I've built, why and what I've learned because for a developer like me, doing physical tangible things is a great distraction from all the digital.</p><h2 id="it-all-started-with-multiple-ideas-converging">It all started with multiple ideas converging </h2><p>A couple of things lead me to finding this super niche interest of mine. First, my love for board games started as a kid but was truly resurrected in early 2010s when I started studying at the university where we had super active board game group that I still play together with. </p><p>Second, around 2016-2017 I started to think more about what I own and how much clutter and stuff I had and explored ideas of minimalism, leading all the way to me living with no permanent home and all my stuff in my backpack for 5 months in 2018.</p><p>Third, I traveled a lot both domestically and internationally during 2018 and 2019 and I wanted to bring my games with me in the backpack so I could play with people I met in conferences or in the train or while waiting for the delayed trains.</p><p>And finally, I had been playing Pokemon TCG for years and had been experimenting with printing proxies to enable more play testing with cards before investing money into them. So the idea of printing cards, putting them into deck boxes (which I had plenty all around) and playing with them was familiar to me.</p><h2 id="minimal-travel-table-top-game-collection-started-the-hobby">Minimal Travel Table Top Game Collection started the hobby</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/minimal-travel-table-top-game-collection-1.jpeg" class="kg-image" alt="A deck box, tokens, meeples, dice and a pile of cards organized in a beautiful symmetry on a wooden table" /></figure><p>So in December of 2019, I had lot of free time to think about stuff and learn non-work relevant skills so I ended up opening Affinity Designer and starting to design some cards. That turned out into my first project, dubbed <em><a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">Minimal Travel Table Top Game Collection</a> </em>because I wanted to give it a cool name so I could showcase it to my friends. </p><p>The collection packed 6 different games into a package that fit into my pocket. It had games for 2-3 players. Some were pretty straight-forward transformations of existing games into my double-sided cards format and one in particular was a more thought-through transformation of a big box game into this small form factor.</p><p>While the world shut down pretty much a month after I got this set, I have played these games at work and last summer when I was able to escape to the countryside for a moment to hang out with my family. I can't wait to be able to take them with me to conferences when those happen again.</p><h2 id="minimal-travel-table-top-game-collection-2-social-distancing-edition">Minimal Travel Table Top Game Collection 2: Social Distancing Edition</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/12-patrols.jpeg" class="kg-image" alt="9 colorful and beautiful cards spread out in fan form with green, yellow and red cubes and dice at the bottom" /></figure><p>As the spring of 2020 continued forward, it become quite obvious I wouldn't be traveling and playing the games of my first set as much as I had hoped. So I started to think about the second iteration of this set, this time focusing on single player games and focusing even more on the great print-and-play community and games designed there.</p><p>This time, since print-and-play games were already distributed as cards you can print, there wasn't any redesign process. <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/"><em>Minimal Travel Table Top Game Collection 2: Social Distancing Edition</em></a><em> </em>followed the naming conventions of my new "brand".</p><p>I did make a mistake here though. When printing cards with a home printer and cutting with scissors or paper cutter, it's perfectly fine to take an A4 PDF and print it. Things will be good. But when ordering actual cards from a card printing house, you need to take into account bleeds and safe margins and by not redesigning the cards from scratch, I ended up with some games that were not optimally printed. I was bit too lazy and just wanted to get the cards ordered.</p><p>I'm happy that happened with the collection full of solo games because they were still fully playable, just not as high quality as I set my expectations for my own projects to.</p><p>Still, the games are fun and I've had so many good evenings alone at home playing these games. My favourites so far are Switchboard and 12 Patrols which are brilliantly designed and nice puzzles to solve.</p><p>The second set followed the same design principles of the first one: everything should fit a 80-card Ultimate Guard deck box, be fun to play with and look stunning.</p><h2 id="a-set-of-retro-pokemon-tcg-decks-designed-to-be-played-together">A set of retro Pokemon TCG decks designed to be played together</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/pokemon-ex-era-decks.jpeg" class="kg-image" alt="Five different color deck boxes in a fan formation and in front of each there are two Pokemon cards unique to decks that live in those boxes" /></figure><p>Come 2021 and my itch to build some kind of set again was becoming stronger. As I mentioned earlier, playing Pokemon TCG has been a passion of mine since 2014-2015 and I had played hours and hours of it online via the digital PTCGO platform. But it was never the same as playing with physical cards and I wanted to explore some of the older formats before my time. I was getting bit bored by the power creep and staleness of the standard format.</p><p>Luckily, one of the all time best in the game, <a href="https://jklaczpokemon.wordpress.com/">Jason Klaczynski has a blog</a> that had sections for every format and set in the history of the game with decklists that you can build. He called the 2004 EX sets as the Golden Age of Pokemon TCG so I obviously wanted to try that out.</p><p>The problem with Pokemon TCG and playing with decks is that sometimes two decks are not a great experience against each other even though both of them could be fun separately. And if you play with just two decks over and over again, you end up playing with the same strategy all the time.</p><p>So I wanted to build something bit more varied. I took five decks from Jason's blog and printed proxies for those, sleeved them on top of old bulk cards and created a five-deck collection. I've been playing those decks against each other alone (I can't emphasise enough how much I miss playing with other people) and they are pretty fun in all different combinations.</p><h2 id="i-get-a-dinosaur-you-get-a-dinosaur-everyone-gets-a-dinosaur">I get a dinosaur, you get a dinosaur, everyone gets a dinosaur</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/feels.jpg" class="kg-image" alt="A black meeple standing in a circular pen in Draftosaurus card with other meeples standing in the background" /></figure><p>This brings me to my latest project that fits into my pocket. This time I did something bit different again. Instead of building a collection of games, I took a big box game (that is hard to buy in Finland unfortunately) that I had played online and tried to design the elements from scratch to make it feasible to be played with 5 people while still adhering to the <em>fits in deck box and looks stunning</em> design philosophy.</p><p>The game is called <a href="https://boardgamegeek.com/boardgame/264055/draftosaurus">Draftosaurus</a> (it's fun, you should buy it if you find it) and it's a drafting and dinosaur zoo building game for up to 5 players.</p><p>I took the game play elements of different pens where to place dinosaurs and designed those to fit into standard size playing cards. Each player's entire zoo fits into 3 cards (with another map on the backside!) so with 15 cards (and 3 for rule references), you can play a 5 player game. There's also a bag of standard Carcassonne-style meeples that I use as the stand-in for the dinosaurs and a cloth bag for drafting.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/collage.png" class="kg-image" alt="Collage of three pictures: left showing a two player game with 6 cards and some meeples, top right showing a close up of a meeple on a card and bottom right with cloth bag and pile of cards" /></figure><p>Using free icons, self-designed card layouts following the format of the main game and some art work from online, I'm super happy about the end result. Like with previous builds, I don't have the rights to distribute any of the art work and I encourage you to buy the real game so you'll have to stick with these pretty pictures. These projects are built for my personal use only.</p><h2 id="make-your-own-game-on-the-go">Make your own game – on the go</h2><p>My next pocket-sized game is definitely a unique one. It's a game I learned in the university and most people I've played it with had not heard about it before I introduced it to them.</p><p>It's a game best played with creative people who like to have fun. People who take winning too seriously won't have a good time. And the best part is that the game evolves with every gaming session.</p><p>The game is called <a href="https://en.wikipedia.org/wiki/1000_Blank_White_Cards">1000 Blank White Cards</a> or 1kBWC amongst those who like that kind of stuff. To make this game yourself, all you need is pen, paper, scissors and friends or foes. You cut your paper into card-sized chunks (or you can use postits, index cards etc), sit down with friends and start drawing cards.</p><p>The rules of the game are defined by the cards and they can be about anything. You can make cards that give player points when played – but remember, those points only matter if there's another card that says they do.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/1000-blank-all.jpg" class="kg-image" alt="A collection of empty green sleeves, empty cards and two stacks of cards with handwritten text and pictures" /></figure><p>Here's an example from my current set. I have bunch of empty cards (I need to order more when the pandemic is over) and bunch of prewritten ones from my previous sessions with different groups. Currently I have roughly 100-150 cards in my collection.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/1000-blank-cards-examples.jpg" class="kg-image" alt="Eight cards with handwritten test and pictures in two rows of 4" /></figure><p>Here are some random examples from my collection to give you some ideas. <em>Harry Yer A Wizard </em>awards player 123 points if they say a Harry Potter spell. <em>Irrational ideas</em> awards player √-1 points when played. <em>No Service</em> forces a player to discard a point card if they use their phone during the game.</p><h2 id="all-my-pockets-are-full">All my pockets are full</h2><p>Having built multiple deck box sized projects, the entire collections is not so minimal anymore and I definitely can't fit all these into my pockets at the same time. But I can always pick and choose which ones I want to take with me when I travel – in the far future when that's possible again.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/all-games.jpg" class="kg-image" alt="A large red box with open lid and 9 small deck boxes of games inside" /></figure><p>Above you can my Ultimate Guard Smarthive 400+ XenoSkin filled with these games. On the top row from left, there's <strong>1000 Blank White Cards</strong>, <strong>Minimal Travel Table Top Collection 2: Social Distancing Edition, Draftosaurus Card Edition </strong>and<strong> Minimal Travel Table Top Collection.</strong></p><p>On the bottom row are my five <strong>Pokemon TCG EX Era retro decks</strong> and the dice & tokens slide nicely on top of the 1kBWC box on the top row.</p><p>I still own a small collection of big box games (like <a href="https://hamatti.org/posts/arts-crafts-and-software-improving-flamme-rouge/">Flamme Rouge that I've written about before</a> or <a href="https://boardgamegeek.com/boardgame/128721/crisis">Crisis</a> that's so far the only table top project I've backed in Kickstarter). And I'm thinking about buying Dale of Merchants collection and dedicating one or two deck boxes for it so I can pick certain decks with me instead of the full package when I travel.</p><p>By building all these things I've learned so much. I've learned about designing cards and how the printing world works. I've learned a bit about branding and productising these projects even though I'll never sell or distribute them.</p><p>Most importantly, I've built myself a collection of variety of games that I can fit into my backpack and play wherever, whenever once the world heals.</p><p>And I'm 100% sure these are not the last projects I make. One day I want to design my own game (I've dabbled into that territory a couple of times but haven't succeeded in making anything playable yet) and build small pocket size packages to sell.</p>
5 minutes that changed my life
2021-03-24T00:00:00Z
https://hamatti.org/posts/5-minutes-that-changed-my-life/
<p>One night, I ended up in a discussion on Discord about how some websites use a certain type of algorithm to figure out if user's actions are organic-like (meaning human) or bot-like. I remembered seeing something about that in an older talk by <a href="https://hilarymason.com/">Hilary Mason</a> and went to Youtube trying to find it.</p><p>Instead, I found something I had forgotten, watched it and remembered how much that 5 minute talk inspired me.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/l2btv0yUPNQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe><figcaption>If you can't see the Youtube embed, you can watch the talk here: <a href="https://www.youtube.com/watch?v=l2btv0yUPNQ">https://www.youtube.com/watch?v=l2btv0yUPNQ</a></figcaption></figure><p>It's a talk by Hilary Mason in IgniteNYC from 2009. In 2011, I had studied in the university for a year and was very far from where I'm right now both mentally and professionally. As I watched the talk, so many memories rushed into my mind.</p><p>This is the talk that inspired me to <strong>become a tech speaker</strong> and start touring meetups and conferences to share my knowledge. It was so captivating and fun and inspiring that I wanted to do the same. 10 years later, I'm doing exactly that, although there's still a long way to reaching Mason's level of quality.</p><p>This the talk that inspired me to <strong>build small personal tools</strong> to help make my life easier or more fun. It taught me that you can do things that are goofy and experimental.</p><p>This is the talk that inspired me on <strong>how to present my projects and talk about them</strong>. This talk was not super technical (I think she later did a longer talk which was more technical in nature) but it caught my attention and told a story in a captivating way focusing on the main features and benefits of the project.</p><p>This is the talk that inspired me to <strong>work with data. </strong>Eventually I ended up in a different direction but this was a very influential moment for me and for the following 3-4 years I was very much focusing on building software around data and doing data visualizations. That interest took me to work in San Francisco in 2014 which on its own was a life-changing experience.</p><p>That's a lot of inspiration from a 5 minute presentation.</p><p><em>I hope that one day, one of my talks or blog posts will have that effect on someone.</em></p>
How to parse command line arguments in Python
2021-03-17T00:00:00Z
https://hamatti.org/posts/how-to-parse-command-line-arguments-in-python/
<p>
Python is a great language for building command line tools. It's versatile,
straight-forward and its tooling and community make it a great candidate for a
language to build your command line tools in.
</p>
<p>
One key feature of command line applications is allowing arguments and options
to further refine the execution of the program when calling it. In this post,
I'll show you how to build support for command line arguments and options to
your Python programs.
</p>
<h2 id="option-1-sys-argv">Option 1: sys.argv</h2>
<p>
A good starting point is Python's standard library's <code>sys.argv</code>. It
has a minimalistic interface to arguments: a single attribute that contains
the name of the script and any arguments passed after that in a list.
</p>
<p>
Let's say, we have a command line tool for summing up numbers that we call
like below:
</p>
<pre><code class="language-bash">python sum.py 1 2 3 4 5</code></pre>
<p>
<code>sys.argv</code> will be a list where the first item is
<code>"sum.py"</code> and the following items are the numbers (as strings):
</p>
<pre><code class="language-python">import sys
print(sys.argv)
# >> ['sum.py', '1', '2', '3', '4', '5']</code></pre>
<p>Hence, for our <code>sum</code> tool, we can do</p>
<pre><code class="language-python">import sys
numbers = [int(num) for num in sys.argv[1:]]
print(sum(numbers))
</code></pre>
<p>
We can even expand our application to be a small calculator by adding the
operation keyword before our numbers:
</p>
<pre><code class="language-python">import sys
import math
operation = sys.argv[1]
values = [int(num) for num in sys.argv[2:]]
if operation == 'sum':
print(sum(values))
elif operation == 'multiply':
print(math.prod(values))
else:
print("Unknown operation", file=sys.stderr)
</code></pre>
<p>This way, our new <code>calculator.py</code> would be called by</p>
<pre><code class="language-bash">python calculator.py sum 4 4</code></pre>
<p>
While <code>sys.argv</code> is great and easy-to-use for simple needs, you run
into its limitations quite fast. It requires you to manually cast arguments to
right formats, it makes supporting optional arguments a challenge and all in
all requires a lot of manual checking and boilerplate code.
</p>
<p>
Let's look at an alternative that helps us build more complex command line
tools, <code>argparse</code>.
</p>
<h2 id="option-2-argparse">Option 2: argparse</h2>
<p>
<a href="https://docs.python.org/3/library/argparse.html"><code>argparse</code> module</a>
is part of Python's standard library just like <code>sys.argv</code>. Where
<code>sys.argv</code> only exposes the command line arguments to the program,
<code>argparse</code> is a purpose-built tool for describing arguments
declaratively. It packs a lot of base functionality behind a clean interface.
</p>
<p>
While the module itself has a clean interface, I find its documentation a bit
difficult to follow. Every time I end up building command line tools with
Python, I have to read through a bunch of blog posts and Stack Overflow posts
to get to where I want to go.
</p>
<h3 id="basic-syntax">Basic syntax</h3>
<pre><code class="language-python">import argparse
parser = argparse.ArgumentParser(description="Calculator")
# [Define arguments here]
args = parser.parse_args()
print(args)</code></pre>
<p>
Even without defining any arguments, <code>argparse</code> gives us one by
default: <code>-h</code> / <code>--help</code>. Try saving above snippet to
<code>calculator.py</code> and run with
<code>python calculator.py --help</code> to see what a default help output
looks like.
</p>
<p>
I've kept the rest of the examples small and contained. When testing them out,
replace <code># [Define arguments here]</code> in the above code snippet with
them and try running the script with different arguments.
</p>
<h3 id="declaring-arguments">Declaring arguments</h3>
<p>
The power of <code>argparse</code>, like I mentioned earlier, is its
declarative nature. All you have to do is to configure the arguments you want
to accept from the user and the module will take care of parsing, converting,
error handling and building a help page for you.
</p>
<p>
Arguments are declared with
<a href="https://docs.python.org/3/library/argparse.html#the-add-argument-method"><code>parser.add_argument</code> method</a>.
</p>
<h4 id="positional-arguments">Positional arguments</h4>
<p>
Let's start by achieving feature parity with our
<code>sys.argv</code> example: a program that takes arbitrary number of
numbers and calculates the sum of them.
</p>
<pre><code class="language-python">parser.add_argument(
'numbers',
type=int,
nargs='*',
help='Numbers to operate on'
)</code></pre>
<p>
Let's break our first <code>add_argument</code> call down and see what's
happening here.
</p>
<ul>
<li>
<code>'numbers'</code> is the name of the argument. All these positional
arguments get stored in variable <code>args.numbers</code> and in the help
page, the user will see its name.
</li>
<li>
<code>type=int</code> does two things: first, it casts the arguments into
integers and secondly, it will show an error to the user if the arguments
are not integers.
</li>
<li>
<code>nargs='*'</code> defines the number of arguments.
<code>*</code> stands for <em>0 or more. </em>Other options are
<code>?</code> which means <em>0 or 1</em>, <code>+</code> which means
<em>1 or more</em> and a number (for example <code>2</code>) which means
<em>exactly 2.</em>
</li>
<li>
<code>help='Numbers to operate on'</code> tells your user how to use the
program and its arguments. Run the program with <code>--help</code> and
you'll see what it looks like.
</li>
</ul>
<h4 id="named-arguments-optional-arguments-flags">
Named arguments / optional arguments / flags
</h4>
<p>
A loved child has many names. While positional arguments are often called
that, the other type of arguments are sometimes referred as named arguments
(as we give them names), optional arguments (as they are optional) or flags
(for named arguments that are boolean values).
</p>
<p>
To differentiate these from positional arguments, the name of the argument
needs to begin with one or two dashes. <code>"operation"</code> is positional,
<code>"--operation"</code> or <code>"-o"</code> are optional/named.
</p>
<h4 id="an-optional-argument">An optional argument</h4>
<p>
In the previous example, we defined positional arguments that are by default
mandatory unless we use <code>*</code> or <code>?</code> value for
<code>nargs</code> that allow the number of arguments to be 0.
</p>
<p>
Optional arguments lets us define arguments that are – as the name suggests –
optional.
</p>
<pre><code class="language-python">parser.add_argument(
'--base',
default=10,
type=int,
help="Number base (for example, 2 for binary, 10 for decimal)"
)</code></pre>
<p>
Here we add an optional argument for doing the calculations in given number
base.
</p>
<ul>
<li>
<code>default=10</code> sets up a default that is only deviated from if the
user provides <code>--base [N]</code> argument.
</li>
</ul>
<h4 id="named-required-argument">Named, required argument</h4>
<p>
Another use case for defining a non-positional argument is to give them a name
and to add support for multiple different arguments without relying on their
positions.
</p>
<pre><code class="language-python">parser.add_argument(
'--operation',
required=True,
help="Operation you want to run on the numbers"
)</code></pre>
<p>
Named arguments can be provided in any order so the following result in the
same end result
</p>
<pre><code class="language-bash">python calculator.py 1 2 3 --operation add
python calculator.py --operation add 1 2 3</code></pre>
<h4 id="supported-values">Supported values</h4>
<p>
In our previous example, the user could write anything as the operation which
is not great UX since we'd only ever support a limited amount of them.
</p>
<p>
To guide our user to define a specific, supported operation, we can provide
<code>choices</code> argument which is a list of accepted values.
</p>
<pre><code class="language-python">parser.add_argument(
'--operation',
choices=['add', 'multiply', 'substract', 'divide'],
default='add',
help="Operation you want to run on the numbers"
)</code></pre>
<h4 id="a-boolean-flag">A boolean flag</h4>
<p>
Sometimes we only care about the existence of an argument and don't need an
associated value. That's called <em>a boolean flag</em>.
</p>
<pre><code class="language-python">parser.add_argument(
'--absolute',
action="store_true",
help="Return absolute value of the result"
)</code></pre>
<ul>
<li>
<code>action</code> argument allows us to customize what we want to do with
the value. <code>store_true</code> and <code>store_false</code> tells our
program to store the corresponding <code>True</code> or
<code>False</code> value respectively.
</li>
<li>
<code>args.absolute</code> will be <code>True</code> if
<code>--absolute</code> flag was passed and <code>False</code> otherwise.
</li>
</ul>
<h4 id="multiple-names">Multiple names</h4>
<p>
Often named arguments have two forms: long form like <code>--help</code> and
short form like <code>-h</code>. You can define multiple names by providing
<code>add_argument</code> multiple positional arguments:
</p>
<pre><code class="language-python">parser.add_argument(
'--absolute',
'-a',
action="store_true"
)</code></pre>
<p>
The value will be stored to the first defined long-form name, in this case
<code>args.absolute</code>. You can change this using
<code>dest</code> parameter to <code>add_argument</code>:
</p>
<pre><code class="language-python">parser.add_argument(
'--absolute',
'-a',
action="store_true",
dest="is_absolute"
)</code></pre>
<p>With this, you can access the value from <code>args.is_absolute</code>.</p>
<h2 id="learn-more">Learn more</h2>
<ul>
<li>
<a href="https://clig.dev/">Command Line Interface Guidelines</a>: An
open-source guide to help you write better command-line programs, taking
traditional UNIX principles and updating them for the modern day.
</li>
<li>
<a href="https://docs.python.org/3/library/argparse.html">argparse documentation</a>
</li>
<li>
<a href="https://hamatti.org/posts/why-i-love-command-line/">Why I love using command line interface?</a>: A blog post about command line interface's greatness
</li>
</ul>
I gamified my own blog
2021-03-10T00:00:00Z
https://hamatti.org/posts/i-gamified-my-own-blog/
<p>Many platforms these days have some gamification features. One of the common features is awarding users/members/players different badges to reward their loyalty and activity in the platform.</p><p>You might have run into these. You get a fuzzy warm feeling inside when the dopamine rush hits you when you get a notification and a beautifully designed badge like this 4 year badge from DEV:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/devto-4-year-badge-notification.png" class="kg-image" alt="Notification for being awarded Four Year Club Badge at DEV Community" /></figure><p>As you're reading this blog post in my own, self-hosted website at <a href="https://hamatti.org/">hamatti.org</a>, there's no gamification features and nobody to award me with funny badges. Or well, at least there wasn't until now.</p><h2 id="introducing-my-own-badges-">Introducing: my own badges.</h2><p><em>I want to preface this by saying that I've done this with a tongue-in-cheek attitude and nobody should really take this seriously or read too much into it.</em></p><p>When <a href="https://twitter.com/0lpeh/status/1359844812992700419">Olavi got his 4 Week Streak badge</a> last month, I started to think about these badges again. One of my goals with this personal blog is to maintain a consistent <strong>blog post every Wednesday</strong> pace. But I wasn't getting any badges to show for it. So I made one, just for some afternoon fun.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/blog-20-weeks-in-a-row.png" class="kg-image" alt="20 weeks in a row" /></figure><p>And then it hit me.</p><p>There's nobody telling me I can't award myself some badges to have a bit of fun and to motivate myself to write. When I hit a new milestone, I design a new badge and add it to my blog. It's been March of 2020 for almost a year now and I need some extra positivity in my life.</p><p>As of today, I'm awarding myself 5 badges.</p><h2 id="self-hosted-blog-badge">Self-hosted blog badge</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/blog-self-hosted.png" class="kg-image" alt="Self-hosted blog badge" /></figure><p>My first badge is for self-hosting a blog. There are a lot of benefits for running your own blog instead of blogging on platforms controlled by others, like DEV or Medium. Full control over your content and how it looks like, how it's shared and what features you can add. Want a paywall? It's your choice. Don't want your readers to be tracked? Your choice.</p><p>Sure, there are also downsides. With some platforms, you gain access to an audience and boost from the community that you might not get with self-hosted blog (my readership is way down compared to when I blogged at DEV) and it takes a bit more time to set up your own rather than registering an account on an existing platform.</p><p>I blogged for almost a decade on platforms run by others so I'm not saying it's a bad thing. But ever since I built my own and gave some serious love to the work that goes into this website, I've been very happy to fully own what I do.</p><h2 id="polyglot-badge">Polyglot badge</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/blog-polygot.png" class="kg-image" alt="Polyglot badge with colorful speech bubbles saying Javascript, Rust, Python, PHP" /></figure><p>I've always been curious about many things and focusing on a single thing like language or other aspect of technology has never been my thing. So I'm proud to wear my Polyglot badge that I awarded for myself for writing technical posts about PHP, Javascript, Python and Rust.</p><h2 id="20-weeks-in-a-row-badge">20 weeks in a row badge</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/03/blog-20-weeks-in-a-row-1.png" class="kg-image" alt="20 weeks in a row" /></figure><p>As I already mentioned, my goal is to keep a consistent schedule and publish every Wednesday. There are a couple of reasons for this otherwise rather arbitrary goal:</p><ol><li>When it comes to content and building an audience, consistency goes a long way. By building a habit of publishing something every Wednesday, people who like my blog posts can learn that every Wednesday something's coming. Maybe it's through social media posts, maybe it's through RSS clients or making a note in their calendar. And when they end up visiting my blog, they can find a lot of good further reading.</li><li>Writing is all about momentum. I fully acknowledge that with my schedule of one post per week, not all my posts are super well researched technical masterpieces. But I know from experience that every week that I don't write and publish, makes it more difficult to get started. So by maintaining a consistent habit of writing regularly, I increase the odds that my creative mind and my habits are hard-wired to write when those great ideas come to mind.</li></ol><h2 id="12-months-without-major-rewrites">12 months without major rewrites</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/blog-12-months-without-major-rewrites.png" class="kg-image" alt="12 months without major rewrites" /></figure><p>Hobby projects are often endless adventures in tinkering, improving and rewriting and that's what makes them fun. I decided to award myself a badge for resisting the urge to rewrite everything from scratch for over a year.</p><p>That has really helped me focus on writing. I have done small improvements over time and the site in general is quite an improvement from what it was a year ago but avoiding major rewrites is a good cause for celebration.</p><p>Many tech blogs start with a "this is how I built my blog" technical description blog post and then they are abandoned. I might have done a few of those in my past as well so this is bit of a sarcastic punch to the collective tech blog industry and developers' urge to rewrite underlying tech instead of writing blog posts.</p><h2 id="100-posts-published">100 posts published</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/blog-100-posts-2.png" class="kg-image" alt="text 100 with two candies in corners" /></figure><p>My main blog that you're reading now, has over 100 blog posts since 2013. That's quite a nice number and a good reason for celebration. When I started blogging, I had hard time coming up with ideas on what to write about and had a lot of moments of impostor syndrome that kept me from blogging.</p><p>These days, blogging and learning in public has become a second nature to me and I love writing and sharing my ideas with the world. Next milestone: 150 posts!</p><h2 id="what-are-your-favorite-badges">What are your favorite badges?</h2><p>If you like badges as much as I do, please share your favorites (either your own or awarded to you by other platforms) by replying to this tweet in Twitter.</p>
Learning Rust #2: Option & Result
2021-03-03T00:00:00Z
https://hamatti.org/posts/learning-rust-2-option-result/
<p>
<em><em>Last December I finally started learning Rust and </em>in January<em>
I built and published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my new monthly blog series that is
defnitely not a tutorial but rather a place for me to keep track of my
learning and write about things I've learned along the way.</em></em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust #1: Pattern Matching</a>
</li>
<li>Learning Rust #2: Option & Result <em>(you are here)</em></li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
Last month I started this series by talking about something I really enjoyed:
pattern matching. And while writing and after sharing that post, I did further
refactoring on the codebase based on ideas and suggestions by the community.
Special thanks to Juho F. for helping out!
</p>
<p>
This month, I want to talk about something that made me struggle a lot and
caused (and still causes) my code writing to slow down considerably:
<code>Option</code> and <code>Result</code> types and how I constantly run
into issues with them.
</p>
<p>
Coming from Python and Javascript development, I'm used to values being most
often just values (or <code>null</code>). I can call a function and
immediately continue with the return value.
</p>
<p>
In Rust, things are done a bit differently. You can still write functions that
take an input and return a value but in many cases, functions return an
<code><a href="https://doc.rust-lang.org/std/option/">Option</a></code> or
<code><a href="https://doc.rust-lang.org/std/result/">Result</a></code> that
are kind of wrappers around the actual values. The reason for this is to add
safety over different kind of errors and missing values.
</p>
<h2 id="what-is-an-option">What is an <code>Option</code>?</h2>
<p>
<code>Option</code> is a type that is always either <code>Some</code> or
<code>None</code>.
</p>
<pre><code class="language-rust">fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}</code></pre>
<p>
The above example is from<a href="https://doc.rust-lang.org/std/option/">
Rust Option's documentation</a>
and is a good example of <code>Option</code>'s usefulness: there's no defined
value for dividing with zero so it returns <code>None</code>. For all other
inputs, it returns <code>Some(value)</code> where the actual result of the
division is wrapped inside a <code>Some</code> type.
</p>
<p>
For me, it's easy to understand why it's implemented that way and I agree that
it's a good way to make the code more explicit about the ways you treat
different end cases. But it can be so frustrating especially when learning
programming.
</p>
<p>There are a couple of ways to get the value from <code>Option</code>s:</p>
<pre><code class="language-rust">// Pattern match to retrieve the value
match result {
// The division was valid
Some(x) => println!("Result: {}", x),
// The division was invalid
None => println!("Cannot divide by 0"),
}</code></pre>
<p>
Continuing from previous example and
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">my previous blog post on the topic</a>, pattern matching gives a clean interface for continuing with the data. I
find this most useful when it's the end use case for the result.
</p>
<pre><code class="language-rust">games.into_iter().for_each(|game| match game {
Some(game) => print_game(&game, use_colors),
None => (),
});</code></pre>
<p>
In <a href="https://github.com/Hamatti/nhl-235">235</a>, I use it for example
to print games that have been parsed correctly and just ignore the ones that
were not. Ignoring them is not a very good way in the long run but in this
case, it's been good enough.
</p>
<p>
Other way is to
<code><a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap">unwrap</a></code>
the <code>Option</code>. The way <code>unwrap</code> method works is that it
returns the value if <code>Option</code> is <code>Some</code> and panics if
it's <code>None</code>. So when using plain <code>unwrap</code>, you need to
figure out the error handling throughout.
</p>
<p>
There are also other variants of unwrap. You can
<code><a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or">unwrap_or</a></code>
which takes a default value that it returns when <code>Option</code> is
<code>None</code>. And
<code><a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else">unwrap_or_else</a></code>
which functions like the previous but takes a function as a parameter and then
runs that on the case of <code>None</code>. Or you can rely on the type
defaults by running
<code><a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_default">unwrap_or_default</a></code>
if all you need is a type default.
</p>
<p>
But there's still more! For added variety, there's
<a href="https://doc.rust-lang.org/rust-by-example/error/option_unwrap/question_mark.html">the <code>?</code> operator</a>. If the <code>Option</code> it's used on is a <code>Some</code>, it will
return the value. If it's a <code>None</code>, it will instead return out from
the function it's called in and return <code>None</code>. So to be able to use
<code>?</code> operator, you need to make your function's return type an
<code>Option</code>.
</p>
<p>
To create an <code>Option</code> yourself, you need to use either
<code>Some(value)</code> or <code>None</code> when assigning to variable or as
a return value from a function.
</p>
<h2 id="what-s-a-result-then">What's a <code>Result</code> then?</h2>
<p>
The other "dual type" (I don't know what to call it) in Rust is
<code><a href="https://doc.rust-lang.org/rust-by-example/error/option_unwrap/question_mark.html">Result</a></code>. Similar to <code>Option</code> above, <code>Result</code> can be one of two
things: it's either an <code>Ok</code> or an <code>Error</code>. To my
understanding so far, <code>Ok</code> basically functions the same than
<code>Some</code> that it just contains the value. <code>Error</code> is more
complex than <code>None</code> in that it will carry the error out from the
function so it can be taken care of elsewhere.
</p>
<p>
Same kind of pattern matching can be used to deal with <code>Result</code>s
than with <code>Option</code>s:
</p>
<pre><code class="language-rust">match fetch_games() {
Ok(scores) => {
let parsed_games = parse_games(scores);
print_games(parsed_games, use_colors);
}
Err(err) => println!("{:?}", err),
};</code></pre>
<p>
In <a href="https://github.com/Hamatti/nhl-235">235</a>, on the top level, I
use above pattern match after fetching the scores from the API. If anything
happens with the network request, the application will print out the error.
One of the tasks on my todo list is to build a bit more user-friendly output
depending on the different cases rather than just printing out raw Rust
errors. I haven't figured out yet how to deal with errors in Rust in more
detail yet though – once I do, I'll write a blog post in this series about it.
</p>
<p>
Like with <code>Option</code>, <code>unwrap</code> and <code>?</code> also
work for <code>Result</code>.
</p>
<h2 id="so-why-do-i-struggle">So why do I struggle?</h2>
<p>
As I've been writing this blog post, everything seems so clear and
straight-forward to me. Still, when actually writing the code, I stumble a
lot. As I mentioned earlier, I come from Python and Javascript where none of
this is used.
</p>
<p>
One thing I've noticed to struggle is that I write some code, calling a
function from standard library or some external library and forget it's using
either <code>Option</code> or <code>Result</code>. Or I forget which one had
which internals and thus, I'm still in a phase with Rust where I need to check
so many things from the documentation all the time.
</p>
<p>
It also feels like you need to take so many more things into consideration. If
you want to use <code>?</code>, you also need to change the way your function
is defined and how the caller works and sometimes that can go on for a few
levels. So my inexperience with the language leads to plenty of constantly
making messy changes all around and occasionally ending up in a dead end I
can't dig myself out of and I end up starting over.
</p>
<p>
And while I know it's mostly about getting used to a different system, I
sometimes feel so dumb when I struggle with making the code not take into
account all different ways it can crash (which is a big reason
<code>Option</code> and <code>Result</code> are built the way they are).
</p>
<h2 id="what-s-up-with-235">What's up with 235?</h2>
<p>
I've been using 235 myself daily since the first developmental version and I'm
loving it. It's the one piece of software I'm most proud of and it's been
answering my needs very well – and from what I've heard of from others, their
needs as well.
</p>
<p>
235 is about to hit version 1.0. I'm getting confident that I've taken care of
most of the major bugs conserning the basic usage and that gives me confidence
in incrementing the version to 1.0.
</p>
<p>
Two things still need to be taken care before that and I'll hopefully get them
done early March once I get a couple of lectures and talks out of my plate.
First thing is to fix
<a href="https://github.com/Hamatti/nhl-235/issues/22">a bug that has a slight chance to appear during the playoffs</a>. Second one is to
<a href="https://github.com/Hamatti/nhl-235/issues/16">make the error handling bit better and provide more user-friendly outputs
when things go wrong</a>.
</p>
<h2 id="if-i-wanna-learn-rust-where-should-i-go">
If I wanna learn Rust, where should I go?
</h2>
<p>
Like I said, don't take these posts as gospel or tutorial. If you wanna learn
Rust, my recommendation as a starting point (and one I started with) is
<a href="https://doc.rust-lang.org/book/">Rust Book</a> and with it,
<a href="https://doc.rust-lang.org/rust-by-example/index.html">Rust by Example</a>. Also, joining your local Rust communities and being active in Twitter by
following <a href="https://twitter.com/rustlang">@rustlang</a> and taking part
in discussions with #rustlang.
</p>
Learning in public
2021-02-24T00:00:00Z
https://hamatti.org/posts/learning-in-public/
<p>
A few weeks ago, I did
<a href="https://www.youtube.com/watch?v=PXEORvNKYvc">a talk at Futurice Tech Weeklies about learning in public </a>and how it makes me so happy how every time when I end up sharing something
either via my blog, in social media or in a talk, I learn something new from
other people who participate in the discussion. You can either watch that talk
or read this post.
</p>
<h2 id="what-s-learning-in-public">What's <em>learning in public</em>?</h2>
<p>
A little bit of background on me: I started blogging about technology in 2013
when I was a junior developer, started doing tech talks in 2014, experimented
with Twitch streaming in 2018 and almost published a podcast in 2021.
</p>
<p>
So when I talk about <em>learning in public</em>, I talk about sharing what
you're doing, what you're learning, what's hard and what's fun. Exposing your
ideas to the public in one form or another and being open about the journey.
The format doesn't matter and neither does the size of the
<em>public audience.</em>
</p>
<p>
This blog that you're reading right now is a prime example. In December, I
wrote about
<a href="https://hamatti.org/posts/advent-of-code-2-borrows-unpacks-and-lots-of-compiler-errors/">my first steps with Rust during Advent of Code</a>
and in January I continued that by starting a new monthly blog series called
<a href="https://hamatti.org/posts/learning-rust-pattern-matching/">Learning Rust</a>. I've also been writing Dev Diary (<a href="https://hamatti.org/posts/dev-diary-1-pokemon-tcg-cube-draft/">1</a>
and <a href="https://hamatti.org/posts/dev-diary-2-working-prototype/">2</a>)
about a project that I'm building with technologies I'm not very familiar of.
</p>
<h2 id="how-did-i-get-started">How did I get started?</h2>
<p>
While I started blogging in 2013 and speaking in 2014, I didn't really have
the <em>learning in public </em>mindset back then. I felt that people who
wrote blog posts were these experts and somewhat authorities on their topics
and that led to me not writing a bunch of posts because I though to myself,
<em>"Someone has already written about this better" </em>or
<em>"I don't have anything to say"</em>.
</p>
<p>
Over the years and especially once I started doing more tech meetups and
conferences in 2018 forward, I started to realize that it's not the case. Most
of the people I met in these events were just like me: learning about things
and sharing what they know. And that started to shift my mindset into being
more open about my learning journey and I started to notice the benefits of
sharing my ideas to an audience.
</p>
<h2 id="real-life-example-1-learning-rust">
Real life example #1: Learning Rust
</h2>
<p>
Like I mentioned, I started learning Rust in December of 2020 and then I built
my first Rust app called
<a href="https://hamatti.github.io/nhl-235">235</a> in January of 2021. As I
was writing the first Learning Rust blog post, I noticed something
interesting.
</p>
<p>
When I normally viewed my code in the code editor, my mind was focused on the
functionality but when I pulled the code into my CMS and inside my blog post,
I looked at it from a perspective of a writer. I started to notice the bad
variable naming, nested structures and other issues of bad code design.
</p>
<p>
Just the fact that I decided to write a blog post, led me to improving my code
quite a lot. Then I shared that code (before and after) in social media and a
fellow developer commented with an idea for improving it further. Thanks to
Juho, the code that is currently in the codebase is even better than my own
refactoring could have reached at that point.
</p>
<h2 id="i-know-it-s-scary">I know, it's scary</h2>
<p>
It is not always easy and fun though. Sharing anything that's not complete or
that you feel like isn't the best, can be scary and intimidating. I think in
our industry, there's this shared fear of
<em>what if someone realizes I don't know something</em>. And somehow, through
all of my impostor syndrome, I've managed to push through that mindset.
</p>
<p>
Tech is a vast knowledge space and nobody can know everything. A few years
ago,
<a href="https://overreacted.io/things-i-dont-know-as-of-2018/">Dan Abramov wrote a blog post sharing things he doesn't know</a>
and that caught a lot of attention in social media and developer circles. I
think it was a great reminder to us all that not even the people who do
amazing work in one part of technology, know it all.
</p>
<p>
So exposing your ideas and incomplete projects and your weaknesses to the
public can be scary but I'd say the benefits are so much better that they
override the fear. And being open about what you claim things to be helps a
lot. When I write about my learning path with Rust, I try to make it obvious
to the reader that I'm a beginner with this language and I'm here to learn. If
someone thinks less of me because of me sharing code that's not perfect, it's
their problem, not mine.
</p>
<h2 id="real-life-example-2-contemporary-documentation">
Real life example #2: Contemporary Documentation
</h2>
<p>
During 2019 and 2020, I did
<a href="https://hamatti.org/talks/contemporary-documentation/">a series of talks about contemporary documentation.</a>
Some in physical meetups and conferences (remember those, they were pretty
cool) and some in remote online events. I even
<a href="https://webbidevaus.fi/56">talked about it in a podcast</a> and some
of my thoughts on the topic ended up
<a href="https://kauppa.intokustannus.fi/kirja/kaikki-koodaa-paivita-itsesi-kaytannon-opas-ajankohtaisiin-digitaitoihin/">in a book</a>.
</p>
<p>
Each time I did the talk to a different audience, I ended up having
interesting discussions. Sometimes it was people suggesting tools that help
teams adhere to good practices when doing documentation. Sometimes it was
people sharing what their team does or asking my opinions on something. Every
single time I went out there to talk and share my ideas, I learned more and
more. And I met amazing people, some of whom became really good friends of
mine.
</p>
<p>
Whenever I think back to those discussions and those moments, I feel so happy.
It's incredible how this industry has this culture of sharing and improving
together that we don't see so much in other industries.
</p>
<h2 id="it-s-not-about-the-millions-but-the-ones">
It's not about the millions but the ones
</h2>
<p>
My blogging is not aimed to build a readership of millions of people. As I
wrote a bit more in detail in
<a href="https://hamatti.org/posts/you-should-start-a-blog-today/">You should start a blog today</a>, there are many other more important priorities that I focus on and learning
in public is definitely out there.
</p>
<p>
I don't need to see Twitter notifications go haywire everytime I share a blog
post. If there's a one comment that starts a discussion, that makes me so
happy because it takes me forward and helps me improve as a professional.
</p>
<p>
In addition to sharing my posts in social media, I end up discussing them a
lot in smaller groups inside developer communities. <em>Public </em>can mean
even inside your team at work if you don't feel comfortable starting with a
global audience in Youtube. For example, do a lunch and learn with your team
once a month on Fridays.
</p>
<p>
I'm so happy that I work with great colleagues and friends at Futurice where
we do an internal Tech Weeklies mini-meetup every Friday. I get to learn from
them, I get to share and when I share, I learn more.
</p>
<h2 id="where-and-how-to-learn-in-public">Where and how to learn in public?</h2>
<p>
Start a blog. Speak in a team lunch and learn or in a local meetup.
Participate in discussions on social media or on developer community
platforms. There are many ways to get started and I think a blog is the
easiest one. You can start by registering at
<a href="https://dev.to/">DEV</a> and write your first post and then share
that with developers you know and in social media.
</p>
<p>
Building an audience takes time. I've been blogging since 2013 (although not
very actively in the early years) and only recently I've started to notice the
effects. And the size of the audience is not what matters but it helps
increase the odds that someone reaches out and starts the discussion. So don't
feel bad if your first few (or you know, 50) blog posts don't generate a lot
of buzz, especially if you're just starting out. Write for your own learning
and documenting, not for the clout in social media and not for the analytics.
</p>
<p>
Speaking in local meetups is also fun. It might feel like you're not
experienced enough but as someone who runs a meetup and regularly chats with
other meetup organizers, pretty much everyone I've met is more than happy to
have beginner speakers and junior developers sharing. It's a platform for
sharing, not an exam on your skills. Just be honest about your skill level and
it will be all fine.
</p>
<h2 id="developers-are-similar-across-different-technologies">
Developers are similar across different technologies
</h2>
<p>A small sidenote about developer communities.</p>
<p>
One of the secret ingredients in me knowing developers and having a lot of dev
friends to talk about technology is that I've been super active in all sorts
of different meetups and conferences even when it's not exactly a technology
that I'm using or into at that point.
</p>
<p>
Because the reality is that many developers are interested in multiple things.
So people I meet in Javascript meetups for example, I might end up bonding
over Rust or building Python backends. Or I might meet people in cyber
security meetups who share my passion for documentation. Or hanging out in a
game development Discord even though I don't make games, I end up in a lot of
discussions about web development.
</p>
<p>
There might not be a specific niche meetup or conference in your area for your
#1 interest. I highly recommend not letting that be a roadblock. Meetups are a
great place to learn about new technologies and meet new people and that's
what I've been doing for the past few years. And some developer communities
like Finnish <a href="http://koodiklinikka.fi/">Koodiklinikka</a> are great
examples of places where people from all different walks of development join
together over a shared interest in technology.
</p>
<p>
And local gatherings (once we can do those again) like Helsinki Dev Lunch or
<a href="https://tampere.devlun.ch/">Tampere Dev Lunch</a> are wonderful for
meeting developers of all sorts regardless of the specific sub-interests. And
if there's not one in your area, why not
<a href="https://hamatti.org/posts/helsinki-dev-lunch/">start your own</a>?
</p>
Pattern matching is coming to Python
2021-02-17T00:00:00Z
https://hamatti.org/posts/pattern-matching-is-coming-to-python/
<p>I was really excited when last week, a set of PEPs (Python Enhancement Proposals) that introduced pattern matching to Python language <a href="https://lwn.net/Articles/845480/">were accepted</a>. Pattern matching is one of those things that ever since I learned to use them in other languages, I've really wanted to have in Python.</p><p>The three accepted proposals are: <a href="https://www.python.org/dev/peps/pep-0634/">PEP 634</a> that introduces the Structural Pattern Matching's specification, <a href="https://www.python.org/dev/peps/pep-0635/">PEP 635</a> that introduces the motivation and rationale and <a href="https://www.python.org/dev/peps/pep-0636/">PEP 636</a> that introduces a tutorial to how it works.</p><p>Pattern matching is scheduled to arrive in <a href="https://www.python.org/dev/peps/pep-0619/">Python 3.10</a>, in the fall of 2021.</p><h2 id="what-is-pattern-matching">What is pattern matching?</h2><p>Pattern matching is a language structure that allows the developer to essentially do two things: to branch out based on the structure of the variable and to bind values to variables for further use. It provides a nicer interface to things that could be done with if/else clauses.</p><p>The example shown in PEP 636 is quite a good example of how pattern matching can make the code much easier to read and follow:</p><pre><code class="language-python">match command.split():
case ["quit"]:
print("Goodbye!")
quit_game()
case ["look"]:
current_room.describe()
case ["get", obj]:
character.get(obj, current_room)
case ["go", direction]:
current_room = current_room.neighbor(direction)
# The rest of your commands go here</code></pre><p>Here, the output of <code>command.split()</code> is matched against different cases:</p><ul><li>An array with a single value <code>"quit"</code></li><li>An array with a single value <code>"look"</code></li><li>An array with two values, first one being <code>"get"</code> and second being anything, bound into variable <code>obj</code></li><li>An array with two values, first one being <code>"go"</code> and second being anything, bound into variable <code>direction</code></li></ul><p>Thanks to the structural pattern matching, we don't have to first branch out based on how many items there are in the array and then branch out based on values but we can do both at the same time and thanks to the binding of values, it's easier to write descriptive names without extra assignments.</p><p>The above example using <code>if/else</code> could look something like:</p><pre><code class="language-python">commands = command.split()
if len(commands) == 1:
operation = commands[0]
if operation == "quit":
print("Goodbye!")
quit_game()
elif operation == "look":
current_room.describe()
elif len(commands) == 2:
[operation, value] = commands
if operation == "get":
character.get(value, current_room)
elif operation == "go":
current_room = current_room.neighbor(value)</code></pre><p>I would argue that the first example is easier to read and makes it easier to understand and reason with the logic of the code.</p><p>To match a default case, this proposal introduces a wild card case <code>_</code>: </p><pre><code class="language-python">case _:
print(f"Sorry, I couldn't understand {command}!")</code></pre><h2 id="or-as-and-guards"><code>or</code>, <code>as</code> and guards</h2><p>In addition to matching just based on a single structure or literal case (or a combination of those), the proposal introduces a couple of additional ways to build powerful patterns.</p><p>With <code>|</code> operator, you can match multiple values:</p><pre><code class="language-python">match direction:
case "left" | "right":
print("Moving horizontally")
case "up" | "down":
print("Moving vertically")
case _:
print("I don't know how to move into that direction")</code></pre><p>And if you want to capture the value of those multiple options, you can use <code>as</code>:</p><pre><code class="language-python">match direction:
case ("left" | "right") as direction:
print(f"Moving horizontally to {direction}")
case ("up" | "down") as direction:
print(f"Moving vertically to {direction}")
case _:
print("I don't know how to move into that direction")</code></pre><p>and finally you can add extra conditions with guards using <code>if</code>:</p><pre><code class="language-python">match points:
case [x, y] if x == y:
print("x == y")
case [x, y] if x > y:
print("x > y")
case [x, y] if x < y:
print("x < y")</code></pre><h2 id="mappings">Mappings</h2><p>Another way you'll be able to match is based on a structure and keys of a dictionary. This is very useful when receiving data in JSON form and parsing it into a dictionary in Python:</p><pre><code class="language-python">data = get_json_data()
match data:
case { "status": status, "data": messages }:
process_messages(messages)
case { "error": error, **rest }:
process_error(error)</code></pre><p>The way this matching works, is that any extra keys will be ignored and the first case will match any <code>data</code> that has keys <code>"status"</code> and <code>"data"</code> and will bind only those values. The second case will match any object that has a key <code>"error"</code> and will bind all the others into variable <code>rest</code>.</p><h2 id="and-the-other-ways">And the other ways</h2><p>To get the full picture of what these proposals will bring to the language, I recommend reading them all through: <a href="https://www.python.org/dev/peps/pep-0634/">PEP 634</a>, <a href="https://www.python.org/dev/peps/pep-0635/">PEP 635</a> and <a href="https://www.python.org/dev/peps/pep-0636/">PEP 636</a>. For example, I didn't write at all about matching objects but there are good examples in the proposals for those as well.</p><h2 id="is-this-a-good-thing">Is this a good thing?</h2><p>The decision to accept the proposal sparked a lot of discussion (unfortunately much of it very unproductive and mean) but I have to say I'm a big fan. Pattern matching helps make code often so much easier to read, as long as it doesn't try to do too much.</p><p>As I've been <a href="https://hamatti.org/posts/learning-rust-pattern-matching/">learning Rust</a> recently, pattern matching has been one of those things I really really like.</p><p>Pattern matching is not the most intuitive features though and it can be hard to grasp at first. Luckily, everything you can do with pattern matching you can do with <code>if/else</code> so beginners don't have to dive deep into learning pattern matching as their first thing when learning programming. </p><h2 id="learn-more">Learn more</h2><ul><li><a href="https://www.python.org/dev/peps/pep-0634/">PEP 634</a>: The specification for the functionality</li><li><a href="https://www.python.org/dev/peps/pep-0635/">PEP 635</a>: The motivation and rationale</li><li><a href="https://www.python.org/dev/peps/pep-0636/">PEP 636</a>: A tutorial</li><li><a href="https://youtu.be/50wYxgFNJFE?t=3729">Structural Pattern Matching in Python: PEPs 634-636</a>: A talk by Daniel Moisset providing great examples</li><li><a href="https://py.watch/get-started-with-pattern-matching-in-python-today-ef4d19c97b7a?sk=ee1b6f2842aaeb3dbd83ed8debe5f95c">Get Started with Pattern Matching in Python, today!</a>: A wonderful blog post on the topic by Alex Hultnér</li></ul>
My home office setup
2021-02-10T00:00:00Z
https://hamatti.org/posts/my-home-office-setup/
<p>Inspired by <a href="https://dev.to/karaluton/workstation-inspiration-5h3j">Kara Luton's post on her setup</a>, I figured I wanted to share mine as well. My setup pre-pandemic and 11 months into the pandemic are very different. A year ago, my setup was mostly "a laptop", I have now made temporary improvements for the time I'm spending all my hours at home.</p><p>I'm a big fan of <em>remote work</em> but not <em>work from home</em>. I like my ability to do my work wherever I am: in the office, at home, on the road, from a pub, from a hotel lobby or in the train. That's why my setup has for a long time been very minimalistic and focusing on things I can bring with me everywhere. I'm very much looking forward to returning to that mode when this pandemic thing is finally under control, hopefully before the end of this year.</p><p>But like many others, this year that has not been a priority so I've made enhancements to my setup. Here's where I do most of my work these days.</p><h2 id="my-desk-setup">My desk setup</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/001-desk.png" class="kg-image" alt="An empty desk with two drawers in front of a flower wallpaper" /></figure><p>Let's start with the desk. It's IKEA's Alex desk with two drawers in front. By the time I bought it, it was one of those desks that was on every Youtuber's home office setup. </p><p>In the left drawer, I keep my electronics: main powerhouse being an Amazon Basics 6-port USB charger that keeps all my USB-powered devices up and running and easy to access.</p><p>In the right drawer, I keep my stickers. Oh, I love stickers and now that there hasn't been live conferences in the past year, I've accumulated a lot of stickers I can't wait to take with me to events in the near future. The right drawer also stores all my random papers that are waiting to be taken away.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/003-display.png" class="kg-image" alt="A blue desk with a black display stand and a 27" ThinkVision display on top" /></figure><p>On top of the desk, I have a generic black display stand that not only lifts the display up for better ergonomics but also provides a nice nook to keep my keyboard in to provide bit more depth on the desktop.</p><p>As my monitor, I recently upgraded to ThinkVision's 27" screen with a USB-C connection so I can get power, video, audio and peripherals with just a single cable.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/004-essentials.png" class="kg-image" alt="A desk with closed macbook connected to a display, white keyboard and black/grey mouse" /></figure><p>Once we add the main computer and peripherals, we start to see a bit more lively setup. I'm a fan of Apple's keyboards, my current one is a full-width corded keyboard but I actually prefer a smaller bluetooth one that reached its end of life and I haven't had the willpower to part with my money to replace it. For a mouse, I tend to really start liking mice that are then quite quickly removed from production. Luckily my Microsoft Mini Explorer mouse has been almost good for a long time (the scroll wheel is busted though which makes me a bit sad).</p><p>As my laptop, I run a 13" Macbook Pro (one for work and one for personal stuff) and I love alpacas, hence the background. I've been using Macbooks since 2014 and haven't looked back since.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/006-mic-headset.png" class="kg-image" alt="A desk with closed macbook connected to a display, white keyboard and black/grey mouse, small black standing speakers and a Yeti microphone with red headphones" /></figure><p>To gain access to both ways of communication, I temporarily upgraded my speaker situation with Logitech Z200s. They are bit big and bulky to my taste but since I'm stuck at home for a while longer still, I figured I'll get some value out of them during the pandemic and sell them once I'm able to pack my backpack again.</p><p>For basic meetings and calls, I use my beautiful red Logitech G433 headset. When I'm doing a talk or presentation, I switch the microphone to Yeti Blue to maximize the audio quality.</p><p>To get good quality video, I have Logitech's (see a pattern?) Streamcam which also has a tripod mount that is brilliant for doing presentations and talks while standing up.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/02/008-streamdeck.png" class="kg-image" alt="A desk with closed macbook connected to a display, white keyboard and black/grey mouse, small black standing speakers and a Yeti microphone with red headphones. A Nintendo Switch on one side of the display and Stream Deck on another" /></figure><p>And finally to add a bit of luxury on top, I have two interesting pieces on top of my display stand. On the left, I have Elgato's Stream Deck which is not only a nice accessory for streaming but also really nice for a lot of custom things. For example, I have a custom profile for all the important shortcuts when playing games in Untap.in platform. No more need to memorize weird shortcuts as I can configure (and even combine) them to Stream Deck with easy visual icons. And of course, I have set up a selection of the best reaction gifs to be posted in Discord and Slack.</p><p>On the right is my Nintendo Switch. I have a full setup with a projector in my living room but at my desk, I want to take a bit of a break every now and then. I use Skull & Co's Jumpgate dock (PSA: 3rd party docks can brick your Switch so it's a calculated risk, I'm not recommending you to buy one) that I bought for two reasons: it's great for travel as you can separate it's Core USB hub and store that in your pocket and then connect the Switch to any TV or projector as you go. And second, the Core also functions as a great small but versatile dongle for my Macbook when needed.</p>
Learning Rust: Pattern Matching
2021-02-03T00:00:00Z
https://hamatti.org/posts/learning-rust-pattern-matching/
<p>
<em>Last December I finally started learning Rust and last month I built and
published my first app with Rust:
<a href="https://hamatti.org/posts/introducing-235/">235</a>.
<strong>Learning Rust </strong>is my new monthly blog series that is
defnitely not a tutorial but rather a place for me to keep track of my
learning and write about things I've learned along the way.</em>
</p>
<h2 id="learning-rust-series">Learning Rust series</h2>
<ul>
<li>Learning Rust #1: Pattern Matching <em>(you are here)</em></li>
<li>
<a href="https://hamatti.org/posts/learning-rust-2-option-result/">Learning Rust #2: Option & Result
</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-3-crates-io-publishing-your-package">Learning Rust #3: crates.io & publishing your package</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-4-parsing-json-with-strong-types/">Learning Rust #4: Parsing JSON with strong types</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-5-rustlings/">Learning Rust #5: Rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-6-ownership/">Learning Rust #6: Understanding ownership in Rust</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-7-learn-from-community">Learning Rust #7: Learn from the community</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-8-whats-next/">Learning Rust #8: What's next?</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-9-a-talk-about-rustlings">Learning Rust #9: A talk about rustlings</a>
</li>
<li>
<a href="https://hamatti.org/posts/learning-rust-10-added-new-feature-with-hashmap/">Learning Rust #10: Added new feature with a HashMap</a>
</li>
</ul>
<p>
<em>My process of learning is reading some guides and docs (with Rust, I
started with <a href="https://doc.rust-lang.org/book/">The Rust Book</a> and
<a href="https://doc.rust-lang.org/rust-by-example/index.html">Rust by Example</a>), then I try to make something that works, ask a lot of questions from the
community, improve, fix bugs, read some more and now I'm writing blog posts
which require me to do more research and help me futher improve my
program.</em>
</p>
<p>
The first concept that I've really liked and that I have not used in other
languages is the
<code><a href="https://doc.rust-lang.org/book/ch06-02-match.html">match</a></code>
operator for pattern matching. In correct use cases, it can make the code nice
and easy to read but it also can end up becoming a messy spaghetti. I'm
currently still trying to figure out when it's best to use and when not.
</p>
<h2 id="basics-of-pattern-matching">Basics of pattern matching</h2>
<p>
First use case I have for match is to map team name abbreviations from the API
into the city names I want to use in the app:
</p>
<pre><code class="language-rust">let str_form = match abbr {
"BOS" => "Boston",
"BUF" => "Buffalo",
"NJD" => "New Jersey",
"NYI" => "NY Islanders",
"NYR" => "NY Rangers",
"PHI" => "Philadelphia",
"PIT" => "Pittsburgh",
"WSH" => "Washington",
"CAR" => "Carolina",
"CHI" => "Chicago",
"CBJ" => "Columbus",
"DAL" => "Dallas",
"DET" => "Detroit",
"FLA" => "Florida",
"NSH" => "Nashville",
"TBL" => "Tampa Bay",
"ANA" => "Anaheim",
"ARI" => "Arizona",
"COL" => "Colorado",
"LAK" => "Los Angeles",
"MIN" => "Minnesota",
"SJS" => "San Jose",
"STL" => "St. Louis",
"VGK" => "Vegas",
"CGY" => "Calgary",
"EDM" => "Edmonton",
"MTL" => "Montreal",
"OTT" => "Ottawa",
"TOR" => "Toronto",
"VAN" => "Vancouver",
"WPG" => "Winnipeg",
_ => "[unknown]",
};</code></pre>
<p>
<em>(Editor's note: while writing this blog post, I'm exposing myself to how
bad my variable naming is in this project. That's partially because so much
mental energy is spent on figuring out how to write Rust that there's not
much left for thinking about good names. I'll fix 'em little by little as I
refactor so it might be that at the moment you're reading this, I have
already renamed them in
<a href="https://github.com/Hamatti/nhl-235">the source</a>.)</em>
</p>
<p>
Another example of how I use <code>match</code> in 235 is when deciding how to
print lines of goals:
</p>
<pre><code class="language-rust">let score_iter = home_scores.into_iter().zip_longest(away_scores.into_iter());
for pair in score_iter {
match pair {
Both(l, r) => print_full(l, r),
Left(l) => print_left(l),
Right(r) => print_right(r),
}
}</code></pre>
<p>
Here, I have <code>score_iter</code> (again, not the greatest name, I admit)
which is a Zip that supports uneven lengths. For example, it could look
something like this (this example is pseudo, the actual data is more complex.
<code>_</code> denotes a missing value):
</p>
<pre><code class="language-python">[
((Appleton, 2), (Boeser, 0)),
(_, (Hoglander, 8)),
(_, (MacEwen, 26)),
(_, (Boeser, 57))
]</code></pre>
<p>
By matching these pairs, each line will either be (<code><a href="https://docs.rs/itertools/0.7.5/itertools/enum.EitherOrBoth.html">itertools::EitherOrBoth::</a></code>)<code>Both</code> which means both values are there,
<code>Left</code> meaning only the left value exists and
<code>Right</code> for only right value existing. Matching these with
<code>match pair</code>, it's nice and clean to run the corresponding print
function.
</p>
<figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2021/01/235-example-right-pair.png" class="kg-image" alt="Text lined up in three columns with second column having 3 more rows than first and third" />
<figcaption>This is what it looks like in 235</figcaption>
</figure>
<p>
This second <code>match</code> example also showcases <em>binding</em> where
we bind the values into variables <code>l</code> and <code>r</code> so we can
refer to them inside the expressions.
</p>
<h2 id="options-and-results">Options and Results</h2>
<p>
Another very handy use case for <code>match</code> is with
<code>Result</code> and <code>Option</code>. My main logic when running
<code>235</code> on the command line is done within
<code>api()</code> function that returns
<code>Result<(), reqwest::Error></code>.
</p>
<pre><code class="language-rust">match api() {
Ok(_) => (),
Err(err) => println!("{:?}", err),
};</code></pre>
<p>
Since <code>Result</code> will either be <code>Ok</code> or <code>Err</code>,
I'm matching <code>Ok</code> with no-op and <code>Err</code> with printing out
the error into the console.
</p>
<p>Example with doing similar with <code>Option</code> is</p>
<pre><code class="language-rust">games.into_iter().for_each(|game| match game {
Some(game) => print_game(&game),
None => (),
})</code></pre>
<p>
Here, if a game was parsed correctly, it will be <code>Some</code> and if it
didn't, it will be <code>None</code> so we can just skip the
<code>None</code> case and only print the games that were parsed correctly
from the data.
</p>
<h2 id="guards">Guards</h2>
<p>
One thing I haven't used yet and only learned about it while doing research
for this blog post is
<a href="https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#extra-conditionals-with-match-guards">guards</a>. They are essentially additional <code>if</code> clauses combined with a
pattern so you can differentiate similar values based on extra condition.
</p>
<p>
The example in
<a href="https://doc.rust-lang.org/rust-by-example/flow_control/match/guard.html">Rust by Example</a>
is this:
</p>
<pre><code class="language-rust">let pair = (2, -2);
match pair {
(x, y) if x == y => println!("These are twins"),
(x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
(x, _) if x % 2 == 1 => println!("The first one is odd"),
_ => println!("No correlation..."),
}</code></pre>
<p>
I haven't yet run into cases where I'd have use for the guards but wanted to
record it here so you'll know it exists.
</p>
<h2 id="conclusion">Conclusion</h2>
<p>
All in all, <code>match</code> might be my favorite syntactic thing in Rust so
far. Sometimes it feels bit too much like a hammer (in sense of
<em>if all you have is a hammer, every problem starts to look like a nail</em>) in that I tend to overuse it even when other means would make more sense.
But I'm sure it's something that I'll find the right balance as I code more
with Rust.
</p>
Introducing: 235
2021-01-27T00:00:00Z
https://hamatti.org/posts/introducing-235/
<p>What's the first thing you do after waking up? Maybe take a shower or drink a cup of coffee? For me and many hockey fans in Finland, it's taking a look at YLE's Teletext on page 235. It's an iconic experience that's been a main stay for years and decades. I can't remember when I first opened the page 235 but it's been a part of my morning routine for as long as I can remember.</p><p>So what's at page 235 that makes it such a culturally important thing? Latest results of NHL games. We Finns tend to love hockey and while most NHL games are played during the night, so often we have to rely on text and colors to catch up with what's going on.</p><h2 id="235-on-command-line">235 on command line</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/01/235.png" class="kg-image" alt="Scores of three NHL games in varying colors indicating goals and state of match" /></figure><p>Now you can get the same experience from the comfort of your command line.</p><p><a href="http://hamatti.github.io/nhl-235">nhl-235</a> is a CLI tool that takes inspiration from the original and provides a comparable experience.</p><p>It can be installed from <a href="https://crates.io/crates/nhl-235">crates.io</a> with <code>cargo install nhl-235</code> or downloading the <a href="https://github.com/Hamatti/nhl-235/releases/latest">latest binaries from GitHub</a>. Since Rust doesn't allow crate names to start with a digit, I recommend doing a symlink to the installed crate to make the experience a really good one.</p><pre><code>ln -s ~/.cargo/bin/nhl-235 /usr/local/bin/235</code></pre><h2 id="built-with-rust">Built with Rust</h2><p>235 is built with <a href="https://www.rust-lang.org/">Rust</a>. I started learning Rust for the first time in December when I attended <a href="http://adventofcode.com/">Advent of Code</a> but struggled to grasp the nuances of Rust. In January, I picked it up again after a month of not coding in Rust and built this tool.</p><p>It's my first published Rust project and I'm so proud and excited that I have made something that excites people and provides real utility. I've learned so much in the process and there's still a lot to learn.</p><p>Basically since the beginning of my programming hobby, I've been a big fan of web as a platform. What I love about the web is that I can deploy changes anytime and they will be immediately available to everyone using it.</p><p>Now, with building a command line tool, it's a very different world. Users will only get the newest updates if they remember to update manually (btw, to do that with crates, re-run <code>cargo install nhl-235</code> – even the symlink will keep on working).</p><h2 id="what-s-still-missing">What's still missing?</h2><p>One important thing that the tool is still missing is the comforting color green: seeing the goals, assists and goalie performance of Finnish NHL players. It's still something I gotta figure out a robust way to implement since the data itself doesn't provide country information on players.</p><p>On another hand, it makes the tool more globally fair as every player gets the same treatment. In one form or another, I'm still commited to add the green to our screens.</p><p>Other than that, I'm not planning to add features. I'll keep on working to make the code better and the application more stable but I want to keep it very simple in design and features. You're welcome to <a href="https://github.com/Hamatti/nhl-235/issues">ask for features or report bugs via GitHub issues</a> though but I won't make any promises.</p><h2 id="open-sourced-and-supported-by-spice-program">Open sourced and supported by Spice Program</h2><p>I believe in open source and sharing my work so others can use it but also to learn from it (I'm not sure if my first Rust project is a place to look for example though). Thus, this project has been published under MIT license.</p><p>On my daily work, I work at <a href="https://futurice.com/">Futurice</a> and one of my favorite perks at the company is the support we offer for our people when it comes to open source. Through <a href="https://spiceprogram.org/">Spice Program</a>, I get extra compensation for working with open source and this project has been supported by that program.</p><h2 id="share-it-use-it-get-in-touch">Share it, use it, get in touch</h2><p>I hope this project brings fun and utility to the lives of terminal using NHL fans. If you know someone who might like it, share this blog post or the project page and tell them about it.</p><p>If you find joy from it, let me know. It makes me very happy when I see <a href="https://twitter.com/hamatti">a tweet</a> or an email (juhamattisantala [at] gmail [dot] com) and hear about people finding my work useful.</p>
Project: card-print-css
2021-01-20T00:00:00Z
https://hamatti.org/posts/project-card-print-css/
<p>There are two things I like a lot: small, well contained tools and things that live in deck boxes. Today's project is one that combines both of these.</p><p>For an effective use of this project, a couple of things are needed: a computer, images in portrait 5:7 aspect ratio, card sleeves, 2.5" x 3.5" cards that you have no longer use for, a printer and printing paper:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2021/01/example-materials.jpeg" class="kg-image" alt="A card sleeve, a Magic the Gathering card upside down and a printed Dark Dragonite card" /></figure><p><a href="https://github.com/Hamatti/card-print-css">hamatti/card-print-css</a> is a project that makes it easy to print out 2.5" x 3.5" cards that fit standard card sleeves. I use it for rapid prototyping when <a href="http://hamatti.org/secret-santa-pnp">I'm attempting to design a board game</a>, testing out <a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">print-and-play games before committing to order actual prints</a> or when play testing different Pokemon TCG decks before purchasing the tournament legal cards. In the picture above, you see a Dark Dragonite card printed on a paper from my most recent testing.</p><p>What <code>card-print-css</code> actually is, is a single CSS file that formats <code><img></code> tags to fit on a page so that when printed on an A4 paper, they will be a good fit for these sleeves. The pictures are slightly smaller than a full card to make it easier to fit with a card (for rapid prototyping of hundreds of cards, it's a blessing).</p><p>The images need to be in 5:7 aspect ratio in portrait mode and that's pretty much all you need. A single sheet of A4 fits 9 cards.</p><p>My workflow is to create the images of the cards, store them all in a folder and then write a small script to turn those filenames into <code><img></code> tags in an HTML page, add <code>card-print-css</code> and start printing. Cutting cards from paper and sleeving them is surprisingly therapeutic.</p><p>This CSS should only be used for pages where you intend to print these card images for sleeving. It uses a lot of <code>!important</code> to make sure accidentally forgotten other CSS doesn't ruin the process. </p>
Thoughts on debugging
2021-01-13T00:00:00Z
https://hamatti.org/posts/thoughts-on-debugging/
<p>Last year I published <a href="https://hamatti.org/guides/humane-guide-to-debugging/">Humane Guide to Debugging Web Apps</a> which lead me to do a few talks and small workshops about the topic. All of them further lead to me having great discussions with developers of all levels of experience. (That's my second favorite benefit of doing talks and writing – the best one being meeting amazing people.)</p><p>What fascinates me in debugging is the way it's a combination of process/mindset and technical tools. That means there are so many things you can learn and improve to make your development process much better and cause much less headache.</p><p>Like my colleague Jan (I highly recommend following him in Twitter!) well said it in Twitter:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Best debugging tool, best to worst:<br />1. Rubber duck<br />2. Shower<br />3. Whiteboard<br />4. Short break<br />5. printf<br />6. An actual debugger<br /><br />Case in point: Was stuck on a problem for two days and just got a very good idea in the shower</p>— Jan van Brügge (@jvanbruegge) <a href="https://twitter.com/jvanbruegge/status/1344636893657505794?ref_src=twsrc%5Etfw">December 31, 2020</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>If you can't see the embed, <a href="https://twitter.com/jvanbruegge/status/1344636893657505794">find the original tweet here</a></figcaption></figure><p>Different problems also require different ways of debugging. Sometimes the problems are in the domain logic abstraction level and then rubber ducks, pen and paper and long walks on the beach are my favorites. Distancing yourself from the code and the technical aspects of software development helps to think about the problem.</p><h2 id="simple-steps-for-technical-debugging-for-beginners">Simple steps for technical debugging for beginners</h2><p>Sometimes the problems are more tangible and concrete however. Sometimes you know exactly what you want to do but the computer is not cooperating with you. I wanted to expand a bit on what I wrote in <a href="https://hamatti.org/guides/humane-guide-to-debugging/">the guide</a>. </p><h3 id="read-the-errors">Read the errors</h3><p>For people new to software development, error messages can be cryptic and even intimidating. There's often a lot of information, especially if the stack trace is long and if you don't have understanding of what those things mean, it can feel easier to just skip them and try to reason with the code.</p><p><strong>Never try to reason with the code.</strong></p><p>What I mean by that is to not go from <em>this code didn't work (errors/does wrong things/does nothing)</em> to <em>let me read the code to see what's wrong. </em>It's not easy to find the mistakes by reading the code.</p><p>Learning how to read the error messages and how to travel back from the error message into your code base has a big impact on your work as a developer. <strong>If you're working with Python, I'll recommend reading through <a href="https://hamatti.org/guides/humane-guide-to-python-errors/">Humane Guide to Python Errors</a>. </strong>For Javascript developers, I recommend starting from <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">MDN's Troubleshooting Javascript page</a>. MDN even has a nice interactive exercise that you can run to get first-hand experience with it!</p><p>Pretty much regardless of your language of choice, one thing you will find from the error message is the <strong>line number for where the error happened</strong>. If you don't know what the error message means, you should copy paste the error into Google and try to see if someone has had similar problems. </p><h3 id="explore-the-current-state-around-the-misbehaving-code">Explore the current state around the misbehaving code</h3><p>Whether you're receiving an error, getting wrong results or nothing happening, <em>exploring the current state</em> is a good next step. <strong><em>State </em></strong>refers to all the variables and their values that are currently available in the code. </p><p>Let's say you're trying to run a <em>map function</em> to an array/list of values and the outcome is wrong.<strong> </strong>Printing out the original array before and after the map function call can give you insights into where the problem lies. If the original array looks good but the result not, you know the culprit is inside the map function. In that case, you can go into that function and either use debugging statements or printing out values to see step by step where the problem is.</p><p>And if the original array is already wrong, you can then go backwards in the code to see where that array came from or was last modified to find the culprit. There's no need to purely guess anything when debugging. Following logical steps, experimenting and attempting different things can lead you to the result.</p><p>That's not to say that debugging something, especially more complex modern web applications would be easy. I once spent days trying to figure out why our application showed wrong data on the web when viewed from New Zealand at a specific time of day. It felt like a lot of dead ends and discussions with colleagues.</p><h3 id="shrink-down-the-problem-area">Shrink down the problem area</h3><p>Identifying the right spot where the wrong code lives is crucial. No matter how many changes you make to your code, you're not gonna make good progress if you are making them in the wrong place. Hence, you should be spending all of your time identifying the correct spot before doing anything else.</p><p>After you think you've found the right place, it's good to then do some testing (if your project has automated testing set up, this would be a great spot to write unit or e2e tests for this part but if you don't you can just test manually). </p><p>Run your code first with it still being in incorrect state. Change something in that code to make sure that the outcome changes (even if not to the correct result yet). This part is important because it validates that you are making changes in the correct spot.</p><p>Now you can work towards making the code correct. Sometimes finding the right spot makes the mistake easy to spot but quite often it doesn't. The smaller you can make the area of suspicious code, the easier it is gonna make sure that your fixes produce a correct outcome.</p><p>Once you've managed to make the fixes, make sure to test with other possible correct and incorrect inputs as well to make sure you didn't accidentally introduce other bugs into your application.</p><h3 id="learn-debugging-tools">Learn debugging tools</h3><p>A good investment of any developer's time and effort is time spent on learning the tools of software development. When it comes to debugging, there are many.</p><p>For frontend web developers, learning Developer Tools (see for <a href="https://developers.google.com/web/tools/chrome-devtools">Chrome</a> and <a href="https://developer.mozilla.org/en-US/docs/Tools">Firefox</a>) are a good place to start. There's a lot to learn there (I'll publish more in-depth look into those later this year, <a href="https://hamatti.org/newsletter">join my newsletter to know when that happens</a>) and you don't have to know every single function before they can become useful.</p><p>Learn about the debugger tool of your preferred language. For Javascript, check out <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger">debugger</a> and for Python check out <a href="https://docs.python.org/3/library/pdb.html">pdb</a>. These tools let you stop the execution of your application and either on command line, in the browser or inside your IDE/code editor to inspect your application state and move forward one step at the time. <a href="https://code.visualstudio.com/docs/editor/debugging">VS Code for example has a really nice debugger toolkit</a> for different languages so you can debug directly in your editor.</p><p>More advanced your tooling and skill of using them is, more complex issues you'll be able to solve. But none of those tools will be helpful if you're not approaching the problem correctly and are just making random guesses. I've noticed recently when helping junior developers that I'm able to help people debug applications written in languages I've never written or read myself simply by following the basic steps and asking questions.</p><h2 id="what-did-i-miss">What did I miss?</h2><p>What are your favorite methods for debugging? Let me know <a href="https://twitter.com/hamatti">in Twitter</a> and let's have a discussion about them.</p>
You should start a blog today
2021-01-06T00:00:00Z
https://hamatti.org/posts/you-should-start-a-blog-today/
<p>
I've been blogging on/off occasionally since 2013. Last year, my goal was to
publish a blog post every Wednesday and other than starting a bit late and
having couple of rough months, I'm really glad with my blogging efforts in
2020.
</p>
<p>
In this blog post, I'll try to convince you that you should start your own
blog right now.
</p>
<blockquote>
<em><em>“A year from now, you will wish you had started today.” — Karen Lamb</em></em>
</blockquote>
<p>
There are different motivations and goals for blogging. I'm gonna talk about a
couple of them but one thing I won't be talking about is monetizing your blog
and making money as a blogger. I don't know anything about it: my blog is not
making me a single penny directly. If you want to learn more about that, I
recommend checking out
<a href="https://bloggingfordevs.com/">Monica Lent's Blogging For Devs</a> and
<a href="https://www.stephaniemorillo.co/links">Stephanie Morillo's content</a>. They also provide good insights into what kind of content to focus on if
you want to build a successful blog – mine is as messy as the thoughts inside
my head.
</p>
<p>
Instead, I'll focus on a couple of other aspects of blogging. I'm ordering
them from the most important to the least important.
</p>
<h2 id="blog-for-yourself">Blog for yourself</h2>
<p>
Start writing a blog for yourself. It doesn't have to be a weekly blog or even
a monthly blog but getting into a habit of writing down your learnings, setups
for projects, your thoughts and ideas or sharing your knowledge is very
helpful. When I need to do some web scraping after a while, I always start
from my
<a href="https://hamatti.org/posts/how-to-scrape-website-with-python-beautifulsoup/">How to scrape a website with Python & BeautifulSoup</a>
blog post to give myself a refresher on how to do it.
</p>
<p>
If you don't want to do a full-blown blog, I recommend adopting
<a href="https://ruk.si/notes">Ruksi's style</a>. He writes short notes on
different topics in a format that doesn't require a lot of prose writing but
still captures the information in easy-to-consume form.
</p>
<p>
I have also been writing about my personal life and other non-professional
topics for years. While it didn't feel like much
<a href="https://hamatti.org/posts/year-in-review-2016/">in 2016 when I wrote my first Year in Review</a>
blog post, in 2020 it's nice to be able to go back and read through my
thoughts, my fears and aspirations and my recollection of each year because
life gets busy and it's easy to forget. Now I have 5 of them written and
collected in my own blog with
<a href="https://hamatti.org/posts/year-in-review-2020/">the 2020 Year in Review published last week</a>. I don't care if anyone else likes those posts or even reads them. I write
those specifically for myself.
</p>
<h2 id="build-a-body-of-work">Build a body of work</h2>
<p>
One huge mindset thing I learned from
<a href="http://jamesstone.com/">my buddy James</a> is to build a
<em>body of work. </em>While my blog does not directly make any money,
indirectly it can have a huge effect on my career. One day, I'll have years
worth of weekly blog posts on technical things, community building and
personal stuff and all of those can contribute positively when I'm applying
for jobs or getting invited to speak in events.
</p>
<p>
If I apply for a conference to talk about topic A, I can link to a few blog
posts and maybe one or two previous talks about the topic because I've spent
years creating that content.
</p>
<p>
I also often blog about things that I get asked about or I run into all the
time like
<a href="https://hamatti.org/guides/humane-guide-to-debugging/">debugging</a>.
Rather than having to explain it all through from the beginning, I can give
people my content as a starting point (or I can check my content to make sure
I'm talking with them about all the important topics). As I've been teaching
programming for a while now, it's become such a timesaver for me.
</p>
<p>
I'm still very early in my building process. But like Karen Lamb said in the
quote in the beginning of this post: when you need it, you wish you had
started way early.
</p>
<h2 id="improve-your-writing-skills">Improve your writing skills</h2>
<p>
One great benefit of regular blog writing is that it will improve your writing
skills. And writing is so much more than just typing words: it's about
structuring your thoughts, finding interesting ways to convey the message and
making it easier to transform your thoughts into writing.
</p>
<p>
All that is beneficial even if you don't plan to become the next Pulizer prize
winner or lifestyle blogger. In today's work life, the ability to communicate
in writing is crucial. Whether it's writing documentation as a developer,
chatting with your colleagues in Slack/Teams/Discord or writing reports, it
all benefits your career.
</p>
<p>
And who knows: maybe you end up like me and actually get really excited about
the writing itself and then you're happy that you started.
</p>
<h2 id="gather-a-following">Gather a following</h2>
<p>
One thing I've learned from creative people in my life is that if you want to
launch something cool one day, it makes a huge difference if you have been
creating and gathering a following much much before the launch. My dream is to
one day build some online learning courses (in style of
<a href="https://wesbos.com/courses">Wes Bos' courses</a>) and writing a blog
and <a href="https://hamatti.org/guides/">Humane Tech Guides</a> is not only a
practice for that but also a way to slowly get on the radar of people who
might be interested in the educational content down the line.
</p>
<p>
That's why at the end of this blog post, there's a newsletter sign up form so
people can sign up for my monthly newsletter in which I share what I've been
up to but also share links to good content by great people that I've enjoyed
that month. Over the years, I hope that newsletter to gain followers who enjoy
what I do and when I eventually get there with the courses, I'll have those
people to tell about it.
</p>
<h2 id="where-to-blog">Where to blog?</h2>
<p>I recommend having your own blog.</p>
<p>
But more important than that is to start somewhere and keep local backups of
your blog posts. For technical blogs, <a href="https://dev.to/">DEV</a> is a
very popular place and it can gain good writers nice visibility. My reader
count plummeted after I moved away from DEV to my own blog but I still prefer
building it long-term on my own blog. And there's always the option to
cross-post in both while pointing the canonical url into your own blog for
extra Google visibility.
</p>
<p>
If you want to start your own, running it on something like
<a href="https://wordpress.com/">Wordpress</a> or
<a href="https://ghost.org/">Ghost </a>are easy ways to start. Or you can do
what I did and build your own site with static site generators, maybe
following my 3-piece tutorial starting here:
<a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/">Building a website with a static site generator, part 1: Setup</a>.
</p>
<p>
Once your blog is running, consider
<a href="https://hamatti.org/posts/your-blog-should-have-an-rss-feed">adding an RSS feed</a>.
</p>
<hr />
<p>Other people have also inspired my with their writing on the topic</p>
<ul>
<li>
<a href="https://www.sarasoueidan.com/desk/just-write/">Just write. by Sara Soueidan</a>
</li>
<li>
<a href="https://mor10.com/blogging-is-dead-long-live-ephemerality/">Blogging is dead. Long live ephemerality. by Morten Rand-Hendriksen</a>
</li>
<li>
<a href="https://www.nateliason.com/blog/be-yourself">Be Yourself, Not a Niche by Nat Eliason</a>
</li>
<li>
<a href="https://rachsmith.com/to-be-whole-is-the-goal/">To be whole is the goal by Rach Smith</a>
</li>
<li>
<a href="https://leportella.com/why-have-a-blog.html/">Why you should have a blog (and write in it) by Leticia Portella</a>
</li>
<li>
<a href="https://cagrimmett.com/thoughts/2022/04/26/why-blog/">Why blog? by Chuck Grimmett</a>
</li>
<li>
<a href="https://tomcritchlow.com/2022/03/08/architecture-blogging/">Building a Digital Homestead, Bit by Brick, by Tom Critchlow</a>
</li>
<li>
<a href="https://matthiasott.com/articles/into-the-personal-website-verse">Into the Personal-Website-Verse by Matthias Ott</a>
</li>
<li>
<a href="https://laurieontech.com/posts/meta-blog/">A blog on blogging by Laurie Barth</a>
</li>
<li>
<a href="https://jamesg.blog/2023/08/06/blog-about-what-you-want/">Blog about what you want by James</a>
</li>
<li>
<a href="https://major.io/p/why-technical-people-should-blog-but-dont/">Why technical people should blog (but don’t) by Major Hayden</a>
</li>
<li>
<a href="https://keith.is/posts/you-should-blog/">
You should blog by Keith Kurson</a>
</li>
<li>
<a href="https://seths.blog/2020/12/the-most-important-blog-post/">The most important blog post by Seth Godin</a>
</li>
<li>
<a href="https://www.alexmolas.com/2023/07/15/nobody-cares-about-your-blog.html">Nobody cares about your blog by Alex Molas</a>
</li>
</ul>
<p>
If you have started a blog recently or started after this, I'd love to know!
Tweet at me (<a href="https://twitter.com/hamatti">@hamatti</a>) with a link
to your blog.
</p>
Year in Review 2020
2020-12-30T00:00:00Z
https://hamatti.org/posts/year-in-review-2020/
<p>
It's never been this difficult to write a post looking back to the previous
year. Usually I've been filling in
<a href="https://yearcompass.com/">Year Compass</a> which is a nice tool for
doing a personal end-of-year retrospective and gently starting to plan the
next one. I've also gone through my calendar to see what fun things have
happened and feeling hopeful for the future.
</p>
<p>
Well, 2020 has been a quite different year. And I've been struggling to figure
out what kind of record of 2020 I want this post to be for myself. Some good
things did happen but most of the year was honestly shit. I hope that as years
pass, this year too will be filled with nostalgia and good memories and not so
much misery and angst.
</p>
<h2 id="2020-before-pandemic">2020 before pandemic</h2>
<p>
The year got a really nice start. Just a few days before the Christmas
holidays, my new role as a developer advocate had been confirmed to continue
as a permanent position and I had high hopes for the year.
</p>
<p>
January and February were filled with great events: hosting meetups, hosting
workshops, giving talks and meeting people. I planned a 6-week trip to
Stockholm, Copenhagen, Berlin, Munich and Stuttgart to meet local developers,
give talks in meetups and conferences and get to know my colleagues in those
cities better. 2020 was gonna be the best year of my career.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/spring-2020.png" class="kg-image" alt="A collage of pictures from various developer events with people and screens" />
</figure>
<p>
As the screenshot from my Instagram page shows, life was good. It was
colorful, filled with good vibes and people I loved doing things with.
</p>
<h2 id="and-then-the-pandemic-happened">And then the pandemic happened</h2>
<p>
Mid-March, the world started to spiral real fast. When the
<em>new reality</em> started to catch up to us, I went into the cancellation
spree. We cancelled bunch of events booked for the week at our office, I had
to cancel my trip and all of my hobbies went down the drain as well.
</p>
<p>
For the first few days, I was optimistic about this all but that optimism
started to disappear quickly. And 2020 has been really defined by the
pandemic. I don't think there's anyone reading who didn't notice that.
</p>
<h2 id="yearning-to-the-countryside">Yearning to the countryside</h2>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2020/12/frances-gunn-QcBAZ7VREHQ-unsplash--1-.jpg" class="kg-image" alt="A red barn in front of a forest and yard" />
<figcaption>
Photo by
<a href="https://unsplash.com/@francesgunn?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Frances Gunn</a>
on
<a href="https://unsplash.com/s/photos/farm?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>
I was born and grew up in the countryside and as I got older and started to
study in the university and get into the work life, it wasn't easy to
integrate into city life. For years, I wanted to get back to the countryside,
to live in a smaller town and not have anything to do with cities.
</p>
<p>
But little by little, I got used to living in the city and appreciating what
it had to offer. And after my years in San Francisco and Helsinki, when I got
into building communities and doing events, there was no way to get me away
from the city. I lived in a small apartment where I mostly sleep and play
video games during the weekends.
</p>
<p>
During 2020, the temptation to move back to countryside grew week by week. It
started all innocently by me seeing pictures from the family who were sitting
on the porch or playing with the dog in the yard. Meanwhile, I took walks in
the rainy concrete jungle of Kallio and felt miserable.
</p>
<p>
I watched <strong>a lot</strong> of Youtubers who build different homesteads
or other things in the countryside. While I have no real interest in mountain
biking, I watched hours and hours of
<a href="https://www.youtube.com/channel/UCu8YylsPiu9XfaQC74Hr_Gw">Berm Peak</a>
where Seth builds a MTB trail on his backyard in rural America.
<a href="https://www.youtube.com/user/allaboutanne18">Anne of All Trades</a>
and her move to Tennesee,
<a href="https://www.youtube.com/channel/UC3FHvW16m_i117IqPnb0nmA">Simple Living Alaska</a>
and few other channels got me in the mood of actually spending some time
looking for land somewhere in the rural Finland. I'm not quite there
realistically (yet) due to work, not being able to drive a car and knowing
that this pandemic will pass one day but I do have to say, there's probably
nothing more (other than getting rid of pandemic) that I wish right now than
not living in an apartment in Helsinki.
</p>
<p>
In the fall, I moved to a bit bigger apartment and that helped a lot with the
mental side. Being able to eat in one room, play games in one and work in
another made a huge difference but nothing compared to the dream of having my
own yard.
</p>
<h2 id="remote-this-digital-that">Remote this, digital that</h2>
<p>
One defining factor of this year is the
<em>remote work, remote fun, remote relationships</em> thing. While I'm a big
fan of remote work, the forced "be alone at home all the time" part wasn't
much fun. My job is based on mainly meeting people in person, the online
experience has been horrible.
</p>
<p>
Online events might be okay for information sharing or selling something, they
are horrible for making new friends, ending up in random discussions and
having a good time. I love playing board games and while we did a lot of that
in different online formats, it's nowhere nearly as enjoyable as being able to
play with friends using the real components and with people in the same room.
</p>
<p>
I did a bunch of online events though this year in different roles. I
<a href="https://hamatti.org/speaking/">spoke in 15+ events</a>, hosted
<a href="https://www.youtube.com/playlist?list=PL4su8m9nd245Q0PgsJhrbU65BmdhayFKQ">some 30 Tech Weeklies events</a>
and participated in a few. We tried doing remote afterworks, online workshops
and other things but none of them took off. Everyone has been feeling quite
tired of staring the screen in yet another video conference call.
</p>
<p>
As Christmas came closer, the pandemic got worse in Finland so even Christmas
this year was spent remotely alone from home. Thanks to Zoom backgrounds, I
didn't have to clean the apartment to join the Christmas call with the family.
</p>
<p>
I live for hugs and I have been hug-deprived this all year. And probably will
for the next year too.
</p>
<p>
Right before the pandemic, I joined a community of amazing people from all
around the world who share my passion for technology and tech
meetups/conferences. Throughout the year, I made a few really good friends
from that group and I hope that one day (fingers crossed, 2022) I get to meet
them in person on some of the conferences that will surely be great once we
get back on with them.
</p>
<h2 id="not-gonna-lie-it-s-been-rough">Not gonna lie, it's been rough</h2>
<p>
Even though I've had the crucial things good and in order this year: my
health, my family's health and my job, it has been far from an easy year. I've
struggled a lot in finding a new way to do my job in an environment where I
can't meet people and people are quite tired of online content as well.
</p>
<p>
That combined with the feeling hopelessness towards the future, I ended up on
a sick leave in the end of the year. I probably should have taken that much
before but hey, I'm a stubborn guy. I've been extremely lucky to have a
supervisor and colleagues who've supported me throughout this year. Twice this
year I spent multiple weeks not being able to sleep due to the 24/7 stress.
Summer holiday and a short trip to the countryside helped a bit but when
returning back to work, I ran into trouble again.
</p>
<p>
I'm the type of person that needs to have a lot to do to maintain momentum.
With the pandemic taking away a big chunk of my daily work and all my hobbies,
I ran into the problem that I had a hard time making even the small things
happen.
</p>
<h2 id="i-did-a-lot-of-writing">I did a lot of writing</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/blog-index.png" class="kg-image" alt="screenshot of the index page for hamatti.org/blog" />
</figure>
<p>
One thing I'm very proud of myself this year is the content I was able to
create. I hate the word <em>content </em>because it feels so distant but the
only other word that comes to mind is <em>multimedia<strong> </strong></em>and
that's quite early 2000s.
</p>
<p>
My goal was to write and publish something
<a href="https://hamatti.org/blog/">on my blog</a> weekly. For most of the
year, I managed to do that. This post included, I shared 41 blog posts (37 in
English listed on the link above, 2 in Finnish not linked there and
<a href="https://hamatti.org/gaming">2 in my gaming page</a>) on my own blog
and I wrote a few to the company blog. I did aforementioned 15+ talks, some of
which can be
<a href="https://hamatti.org/speaking/">found on my website</a> and I visited
3 podcasts (two published in Finnish, one to-be-published in English). And I
published
<a href="https://hamatti.org/guides/humane-guide-to-debugging/">Humane Guide to Debugging Web Apps</a>
which also sparked a collection of tech talks and workshops.
</p>
<p>
Writing is something I want to continue in 2021 and I might have some podcast
ideas of my own, let's see how those pan out. My blog is a messy collection of
things close to my heart: technology, education, community building, fun stuff
and personal things. It's not what someone would call a recipe for success but
it's not built to be a successful blog – it's built to be a venue where I can
get my thoughts written down and shared with the world.
</p>
<p>
And while we're being honest here, I still feel very inadequate in terms of
writing and publishing content. I follow amazing people who seem to do way
better quality content, publish more often and offer amazing things for the
people reading and watching.
</p>
<h3 id="some-of-my-favorite-picks-from-my-blog">
Some of my favorite picks from my blog
</h3>
<p>
It's very possible that you haven't read through my blog before this. Not many
have. So I wanted to share some of the posts that I felt were some of my
better pieces:
</p>
<ul>
<li>
<strong><a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">Minimal Travel Table Top Collection</a> </strong>was not only a project I loved, it turned out to be a nice discussion
starter. Of course, I built it just before the pandemic, so I still haven't
been able to really test it in the travel & conference setting for which
I built it. But the games are great, it looks fabulous and I've had fun
playing the games. I also built a pandemic version with solo games, linked
at the bottom of that post.
</li>
<li>
<strong>Building a website with a static site generator (<a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/">part 1</a>,
<a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-2-eleventy/">part 2</a>,
<a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-3-domain-analytics-and-forms/">part 3</a>) </strong>is a trilogy of posts walking you through how to build and deploy a website
with modern technology stack.
</li>
<li>
With the events going online, I wrote down
<strong><a href="https://hamatti.org/posts/tips-for-giving-talks-online/">my tips for being a great speaker in the era of remote</a></strong>. 6 months later, I have bunch of new ideas so there might be a follow up
to that coming in early 2021.
</li>
<li>
Honeypot launched their new .cult community and I got to write a couple of
posts (one of them being featured in the very first newsletter 😍). In
<strong><a href="https://cult.honeypot.io/reads/build-hobby-projects-get-first-job">Building Hobby Projects to Help Get Your First Programming Job</a> </strong>I wrote about ideas on how to come up with project ideas, how to present
them and how to use them as a discussion driver when applying for jobs.
</li>
<li>
<strong><a href="https://hamatti.org/posts/how-to-ask-help-for-technical-problems/">How to ask help for technical problems?</a></strong>
was born from helping people out and being helped myself in different online
communities. If you're having trouble, asking help is great but making it
easy for others to help you is even better.
</li>
<li>
For people learning programming, I wrote
<strong><a href="https://hamatti.org/posts/functions-101/">Functions 101</a>, </strong>which explains functions for those very early in their programming journey
who might be struggling to grasp them.
</li>
</ul>
<h3 id="oh-i-also-have-a-newsletter-now-">Oh, I also have a newsletter now!</h3>
<p>
I started to experiment with
<a href="http://hamatti.org/newsletter">a Very Personal Newsletter</a> this
summer. I send out a monthly email to my subscribers talking about my life,
what I've been up to and sharing interesting things other amazing people in
the world are making.
</p>
<h2 id="tell-me-about-juhis-">Tell me about Juhis!</h2>
<!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Hu8G-n7CUtE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe><!--kg-card-end: html-->
<p>
I published a video (made by Eero and Lauri at
<a href="https://hamatti.org/p/997c8e46-8e0a-4425-a2fe-204c803b0298/Awara.fi">Awara.fi</a>) where
people from my communities told to everyone who I am. It's hard to talk about
myself so I recruited some of the best people in the world to do that for me.
</p>
<p>I'm so happy about how that video turned out.</p>
<h2 id="so-many-video-games">So many video games</h2>
<p>
With my hobbies gone and work being less hectic, I had a lot of time to catch
up with video games. Between March 13th and December 31st, I played almost 100
video games. Some old favorites, some older games I wanted to play but didn't
have time before and quite a few new games as well. Way too many to list here
but some of my favorites that I played in 2020 were Last of Us 2, Lonely
Mountains: Downhill, Fall Guys, Uncharted 1-4, NHL/NBA/FIFA/Madden sports
block and Far Cry 5 (it was the surprise of the year definitely).
</p>
<p>
And Pokemon. I played so much Pokemon (especially TCG Online). Hundreds of
hours.
</p>
<p>
For those online parties and get-togethers we also played countless hours of
Jackbox Party Games.
</p>
<p>
(PS. I also watched way more Netflix/Prime Video/Viaplay/Disney+ than I care
to admit.)
</p>
<h2 id="2021-please-be-better">2021, please be better</h2>
<p>
I'm afraid to be too hopeful of 2021. With the vaccines now in circulation,
I'd like to be optimistic but there's gonna be a long long spring before we
get to the summer with hopefully more human contacts and fall when we can
hopefully start doing tech events again.
</p>
<p>
I crashed and burned too many times with my optimism this year that letting
the sparkles of hope into my life feels scary.
</p>
<p>
For 2021, my only hope is that we manage to get rid of this damn pandemic. I
want to eat lunch with friends, sit and write in pubs and travel back home. I
want to hug everyone, do some high fives and have fun with friends. I want to
see my new nephew and teach my older nephews how to play Pokemon TCG. I want
to spend a week in Turku doing nothing but playing board games all day long.
You know, the normal stuff.
</p>
<p>
And maybe, if this all goes worse, I might want to buy a small farm, get a few
donkeys and leave the city behind.
</p>
<p>
As soon as this pandemic is over, I'll move again no matter what. Whether it's
another city apartment or somewhere in the wilderness, I want these walls and
lovely flower wallpapers to not haunt me after the pandemic anymore. Time for
a fresh start. Let's hope it happens in 2021.
</p>
<p>
While waiting for the pandemic to go away, my new year's resolution is to get
back to doing hard things. To shrug off the rut I've collected during this
year and actively challenge myself to do things that are not easy. Things that
would take me forward after a year of standing still.
</p>
Merry Christmas my friends
2020-12-23T00:00:00Z
https://hamatti.org/posts/merry-christmas/
<p>Hey!</p><p>It's holiday time so this week's blog is just gonna be a merry Christmas celebration post. Next week, I'll roll in with the end of year post looking back into 2020 and reflecting on this very unusual year.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2020/12/hello-ruby-christmas.png" class="kg-image" alt="Christmas card with Ruby, computer and Tux walking in snowfall" /><figcaption>Hello Ruby Christmas Card from <a href="https://www.helloruby.com/play/23">https://www.helloruby.com/play/23</a></figcaption></figure><p>I love these Christmas cards from Linda Liukas. I got a big bunch of them few years ago when they were available and have been giving them out to people in my communities every Christmas and this year I sent my last ones to some of my favorite people around the globe. For those who didn't get one in mail, this one's for you.</p>
Advent of Code #3: Slowing down
2020-12-16T00:00:00Z
https://hamatti.org/posts/advent-of-code-3-slowing-down/
<p>As of today, I'm at 15 stars. And I've been on that for quite a while now. Basically since the last blog post, the usual happened to me: I hit a few hard bumps and lost momentum. So instead of joyfully being excited about what I've been learning and how I've solved this past week's puzzles, I wanna talk about two things related.</p><h2 id="why-am-i-struggling-with-rust">Why am I struggling with Rust?</h2><p>During this month, when I've told people I picked up Rust to learn while solving the puzzles, I've been getting a very similar response. People have been surprised that I decided to do such thing, which I guess means that I took quite an extra challenge for myself.</p><p>Big reason I lost momentum was that while solving these puzzles, reading docs and trying to understand the underlying way Rust is meant to be written, I couldn't become comfortable with the core stuff. Coming from a background of developing in languages like Javascript and Python, the first and biggest hurdle is understanding the reference/pointer system. I do kinda understand it on a theory level but then I constantly run into problems with it and it's understandable that after spending 15 minutes of writing a loop to add items to a vector, it's not fun to code.</p><p>I understood <em>why</em> things I tried didn't work but I it would take all my brain power to figure out a workaround to get around it. I might have a vector of <code>&str</code> or <code>&String</code> and I needed to filter out certain ones based on some condition and maybe some sort of string manipulation. In JS/Python, I could just make an empty list, loop over the input and add things to the list. In Rust, I constantly ran into problems with variables created inside loops since I couldn't add references to those into the vector because the variable would get dropped at the end of the loop.</p><p>Second challenge that was way smaller, but still something that caused inertia and slowed me down, was static typing. And especially in a way that I had a lot of trouble getting used to the many different return types of different functions. Maps, filters, collects, lines, chars and all the other functions seemed to all return very different things and I always struggle to figure out how to convert those into vectors that I could actually then work with. So often I was told that I cannot translate a result of some of those collection functions into <code>Vec<&str></code> or whatever I ended up needing.</p><p>The second thing is something I know will get easier quite fast by just doing them more and learning the patterns but with the daily pace of Advent of Code, it was just too much inertia, especially when having so much trouble with the first issue. I'll probably revisit some of the puzzles with maybe Python or Javascript once my holiday kicks in this Friday and I'll definitely revisit Rust with a different type of project (most likely building some command line tools) in the future.</p><h2 id="community-around-advent-of-code-is-lovely">Community around Advent of Code is lovely</h2><p>The one thing that has been keeping me really excited about Advent of Code throughout the week even when I haven't been solving them myself has been the community. I've talked about it briefly in the previous two blog posts but wanted to write more here.</p><p>I've gotten a few dozen people to join and start solving puzzles this year by sharing my experiences and helping others with their puzzles and programming problems. I've been actively discussing these in four different developer communities, my blog and social media and I've met and ended up talking with quite a lot of new people thanks to this.</p><p>It makes me so happy, because that's what I do. I'll never be the best developer or a substance expert when it comes to coding. But if I can help other people get excited about stuff, help them find each other and feel the sense of achievement when they solve the puzzles, that makes my day.</p><p>By sharing your code, you help someone learn. I've read a lot of other people's Rust Advent of Code solutions to see how they structure their solutions, what functions they use and how they manage all the things I struggle with when using the language. Your solutions don't have to be the best practice or optimal solution for it to be helpful to others. So I encourage you to share your puzzle solutions with people, for example in GitHub.</p>
Advent of Code #2: Borrows, unwraps and lots of compiler errors
2020-12-09T00:00:00Z
https://hamatti.org/posts/advent-of-code-2-borrows-unpacks-and-lots-of-compiler-errors/
<p>First full week of learning Rust with Advent of Code is in the bag and I'm so happy I've gotten this far. Due to the pandemic, there's more time to get these done every day and that's really helping me finish. Here are my thoughts from days 2 to 8.</p><h2 id="feeling-rust-y">Feeling Rust-y?</h2><p>I didn't remember learning was this tough. I did talk about it bit in <a href="https://hamatti.org/posts/advent-of-code-1-getting-started/">the first post of the series</a> but this week definitely showed its true colors to me. Have you played those video games where in the intro, you get to play with all the gadgets and abilities and then they are taken away? I feel like that right now.</p><p>I see a task (like reading the input data into a proper data structure) and I feel confident because with languages like Python or Javascript it would take me a few seconds and no retries to get it done. With Rust, it's a different story. Since I'm not yet familiar with the standard library and what each type has in its toolbox, I struggle <strong>a lot</strong> with stuff that I normally don't even break a sweat. Splitting a string and mapping over that data? Yeah, I'll either run into a hundred compiler errors or google furiously to figure it out. And it's so frustrating.</p><p>My inner compass on what types different operations return and especially when to reference with <code>&</code>, when to use variable as-is and when to dereference with <code>*</code> is basically mostly still based on me guessing and/or following compiler errors.</p><p>I've started to call it Compiler Error Driven Development. It leads to functioning software, which I'm really happy about, given how much I give talks and workshops on debugging but it doesn't always feel great and it definitely doesn't create idiomatic Rust code.</p><h2 id="so-much-unwrapping-and-it-s-not-even-christmas-eve-yet">So much unwrapping and it's not even Christmas Eve yet</h2><p>One of the new things for me is the use of <code>Option</code> type. Instead of returning a number, I'd return a <code>Some(number)</code> and then do a lot of <code>unwrap</code>ping on the other side. On a theory level, I do kinda understand it but it's difficult to wrap my head around because it still feels like a lot of boilerplate code with all of the operations I run.</p><p>On <a href="https://github.com/Hamatti/adventofcode-2020/blob/master/src/bin/day02.rs">Day 2</a>, I laughed at the amount of <code>unwrap</code>s in my code (it was the first time I really used them a lot) and it was such a good Christmas pun that it made me feel Christmas-y. I replaced some of them with <code>?</code>s but I still don't have a solid understanding of which one to use and when and what are the implications with the rest of the code with <code>Option</code>s and <code>Result</code>s.</p><h2 id="different-approaches-to-learning">Different approaches to learning</h2><p>I've been mostly on the other side of the table over the past years. I've been teaching Python and Javascript and helping junior developers get started with software development. Within the world of Python and Javascript, my learning has mostly been about reading the docs and figuring out how a library works.</p><p>With Rust, I'm in a whole new situation. And since I have a strong background with a few (very different) languages, I've noticed that I'm much more looking for <em>best practices</em> than <em>how to run the code</em> type of advice. I can make the code run and return a result (so far, the right result) but I feel utterly lost not knowing if my code makes any sense in terms of writing Rust code.</p><p>Luckily, Rust seems to be a choice of language for many people this year with Advent of Code so reading through the solutions of multiple different people as well as getting advice and code reviews from fellow developers each day has been very helpful. And for once, I've been merciful to myself as I'm so early in the learning process.</p><h2 id="borrow-this-borrow-that">Borrow this, borrow that</h2><p>Last week I mentioned how my experience with languages like C, C++ and Rust is basically zero. I remember my struggle with references and pointers and all that from that only time I touched C++ in university. And it's all coming back to me with Rust.</p><p>Week and a half into my Rust journey and I still most of the time just guess what kind of prefix my variables need and when. The Rust compiler is very kind and I <a href="https://twitter.com/Hamatti/status/1334159719628500994">tweeted last week about how the language matters</a>. When I'm stressed about all the red in my terminal, a kind tone of voice the compiler has definitely makes it easier to deal with.</p><p>On <a href="https://github.com/Hamatti/adventofcode-2020/blob/master/src/bin/day03.rs">Day 3</a>, I especially got so many "You're borrowing here" and I just couldn't understand what the concept meant. I was directed to do some more reading and finally I understood what it means – even though it doesn't mean I still know how to do it right the first time. But on some level, I do now know what borrowing, moving and ownership mean in terms of Rust and variables.</p><p>As the week progressed though, I have to say that the very technical parts of Rust compiler errors started to get bit challenging to follow and increased the stress factor.</p><h2 id="day-4-threw-wrenches-into-many-people-s-work">Day 4 threw wrenches into many people's work</h2><p>I struggled so much with <a href="https://github.com/Hamatti/adventofcode-2020/blob/master/src/bin/day04.rs">Day 4</a>. I got the first part done quite nicely but then the second one stumped me totally. And talking with others, I wasn't alone. Interestingly enough, for most of the people I talked with, the exact issues were unique to each one of us but all very related.</p><p>The solution for all of us was the same though: reading through the specification more carefully. I had a dozen nicely working tests and manual inspection of the debug output seemed to be right as well but still Advent of Code kept telling me I was wrong. I had missed a part of the specification (or more accurately, I didn't realize the input for height could be missing the <code>cm</code>/<code>in</code> part).</p><p>One thing I realized when struggling with it is how nice it's to do these Advent of Code puzzles that have very well defined inputs and outputs and rules. In real life, the situation is very different and too often we end up developing something with very unclear requirements. So AoC is great change of scenery for that.</p><h2 id="a-few-nice-days-before-the-big-challenge">A few nice days before the Big Challenge</h2><p>Then days <a href="https://github.com/Hamatti/adventofcode-2020/blob/master/src/bin/day05.rs">5</a> and <a href="https://github.com/Hamatti/adventofcode-2020/blob/master/src/bin/day06.rs">6</a> were really nice for me. Maybe I had gotten a slight bit better with Rust or the puzzles ended up being in my wheelhouse but I basically cruised through them in a short time with almost no issues at all.</p><p>That was all nice and good but then came Day 7. I read through the puzzle on Monday but decided to do other things as I wasn't feeling super puzzle-y that day. When I returned to it on Tuesday, I just ended up hitting brick wall after brick wall.</p><p>The combination of me struggling with Rust (especially with manipulating collections like vectors and hashmaps inside loops), the fact that I've never really done anything with graph or tree structures and a challenging puzzle and I spent hour after hour after hour trying to make something happen but failing over and over again. I can't remember when was the previous time I've felt that frustrated.</p><p>I got a lot of help from people at Futurice and Koodiklinikka (especially thanks to <a href="https://twitter.com/koivunej">Joonas K.</a> for patiently walking me through stuff) but I had to admit my limitations this time. Day 7 defeated me triumphantly.</p><h2 id="bounce-back-with-8">Bounce back with 8</h2><p>On the other hand, solving <a href="https://github.com/Hamatti/adventofcode-2020/blob/master/src/bin/day08.rs">day 8</a> was once again fun and somewhat straight forward. I got my first solution for part 2 by cheating a bit (I ran the code once for one option and then manually changing the code and running again) since I wasn't quite sure my base solution was gonna be right. Once I learned it indeed was right, I was able to refactor the code to do that automatically without me having to change things in the code.</p><p>It was also probably the most interesting to me in terms of what was being built and I hope we get to do something similar in the future as well.</p><p>Every now and then during the week I felt like I was learning Rust and then every now and then I got cold water to my face and realized I still don't understand how it works or how to code with it. </p><p>As the month goes along and I hopefully learn a bit more, I'll definitely revisit my thoughts on Rust each Wednesday on these blog posts. And since these weekly posts are starting to end up being very flow of thought journaling more than well edited blog posts, I'll definitely write a more comprehensive wrap up post at the end of all this to collect my thoughts. </p>
Advent of Code #1: Getting Started
2020-12-02T00:00:00Z
https://hamatti.org/posts/advent-of-code-1-getting-started/
<p>Every year I embark on a magical Christmas adventure with <a href="https://adventofcode.com/">Advent of Code</a>. Quite often, I end up working on the first few days' challenges and then it takes a backseat to other things in life. This year – like every other before it – I'm serious though and want to try to finish the entire challenge and document my learnings meanwhile.</p><p>This series won't be a tutorial nor a <em>"this is how you solve these challenges"</em> guide but rather a journal of my progress with Advent of Code and Rust. If you're afraid of being spoiled on those challenges, try to solve them first before reading further as I'll show and talk about some code as well.</p><h2 id="why-am-i-so-excited-about-code-puzzles">Why am I so excited about code puzzles?</h2><p>When I was a kid, a chocolate advent calendar was the best way to get hyped for Christmas. Every morning, I started the day by opening a new window and find chocolate that was usually very sub-par in quality but it was never about the taste of chocolate. It was about the freedom to eat chocolate in the morning and the excitement of days leading towards the Christmas Eve (which is when we Finns celebrate).</p><p>As a web developer occasionally working on client projects but mostly focusing on developer community building, I'm often looking to do something fun with code, experimenting with new technologies and keep learning but I don't want to start a multi-year project. Advent of Code is perfect for that. I can do snack-sized puzzles with some technology without any pressure of finishing or writing amazing code.</p><h2 id="this-year-i-m-using-rust">This year I'm using Rust</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/11/rust.png" class="kg-image" /></figure><p>I have never really done any systems programming. I did one combined C & C++ course in university but that's pretty much it. Last year, when I was speaking at PyCon Estonia, I met <a href="https://twitter.com/DomWeldon">Dom Weldon</a> and saw his talk <a href="https://www.youtube.com/watch?v=m1VCAp6hm0M">Oxidizing MyPy</a> in which he talked about using Rust and Python together. That sparked an interest in me to get started with Rust.</p><p>It took me a year to actually do anything with it. I was kinda saving Rust for another project but since I realized I might never make that happen, I'll start learning Rust with Advent of Code.</p><p>One of the reasons I wanna learn Rust is to build command line tools. I have <a href="https://hamatti.org/posts/why-i-love-command-line/">previously written about my love for CLI tools</a> if you wanna know why. I've seen Rust becoming quite popular for CLI tools and I want to get into that groove.</p><h2 id="days-0-to-1">Days 0 to 1</h2><h3 id="first-contact-with-rust">First contact with Rust</h3><p>I wanted to get a good start with a new technology so I took a head start and spent the evening of Nov 30th to make sure I had a setup where I can start working on.</p><p>So I started by installing Rust and Cargo, read <a href="https://doc.rust-lang.org/book/title-page.html">the first few chapters of Rust docs</a>, built the number guessing game in the documentation to make sure I know the very basics and built a template for unit tests.</p><p>For me, Advent of Code is perfect place to use <a href="https://en.wikipedia.org/wiki/Test-driven_development">Test-Driven Development</a> and I've figured that if I learn how to write and run tests with new technology from the very first touchpoint, it's more likely I'll write good tests when I start using the language in more serious projects.</p><p>Another thing I wanted to learn is the conventions of documentation in Rust. And Rust is great for that. Already on Day 0, I fell in love with <code><a href="https://doc.rust-lang.org/cargo/commands/cargo-doc.html">cargo doc</a></code> that generates local documentation, not only from your own code but of your dependencies as well. How is that not a main feature in all languages and ecosystems? It is so smart. And it encourages good documentation of your own crates and functions as well. Rustdoc has <a href="https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html">great documentation</a> for how to write and build documentation.</p><h3 id="december-1st">December 1st</h3><p>First interesting thing I noticed with Rust is that it's recommended to keep unit tests in the same files as the functionality. Rust uses something called <em><a href="https://doc.rust-lang.org/reference/conditional-compilation.html">conditional compilation</a> </em>which means that you can define in source level, if you want something to be compiled to the final binary or in this case, only when running tests. That can be defined by annotating a function with <code>#[cfg(test)]</code>.</p><p>A bit similar functionality that I've used before is Python's <a href="https://docs.python.org/3/library/doctest.html">doctest</a> that allows tests to be included in docstrings of functions. Rust also has similar functionality but from a different perspective. Developers are encouraged to add an example or two into their documentation of functions so it's easier for readers to use the functionality. To make sure those documentation examples work, rustdoc runs those examples and tests them for you. <strong>What a great way to keep documentation and code in sync.</strong></p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/12/aoc-2020-1st-star.png" class="kg-image" alt="Hamatti 1*" /></figure><p>And then it happened. I wrote my very first Rust code (by furiously googling and copy-pasting code together) and <strong>IT WORKED</strong>. I got my very first star for this year. My code didn't look very beautiful and it probably wasn't the most idiomatic Rust. I'm guessing it looks like Python or Javascript developer's first Rust code.</p><p>So I submitted it for code review to my colleagues at <a href="https://futurice.com/">Futurice</a> and fellow Rust developers at <a href="https://koodiklinikka.fi/">Koodiklinikka</a>. A great way to improve fast is to take a challenge, figure it out by reading docs and googling and then having a discussion about it with other developers. Thanks especially to <a href="https://twitter.com/akx">Aarni</a> and Jaakko for helping me improve my first bits of code.</p><h2 id="learning-new-is-exhausting">Learning new is exhausting</h2><p>I've been coding more or less actively for a few decades since my teenage years. I noticed something very interesting (which reminded me it's been a while since I've been actively learning something new): even though I'm very familiar with basic concepts like reading input and looping and all that, when learning a new language like Rust, I keep completely forgetting all the basics.</p><p>I decided to build my solution so that it reads the result from standard input rather than file and Jaakko did the opposite. After discussing our solutions and reviewing our code, I totally forgot I had done that, ran my code and waited for half a minute wondering why my code is so slow. I had forgotten to provide the input into standard input so the code wasn't doing anything.</p><p>On the bright side, I love that kind of really intensive learning. It tickles my brain in just the right way and gets me excited with stuff.</p><p>The first couple of days of Advent of Code are usually quite nice warmup challenges. They are helpful in making sure my setup works and my code is run correctly. This year was not an exception to that rule and I'm happy about that because there's always an extra added layer of complexity when setting new things up.</p><h2 id="my-solutions">My solutions</h2><p>I update my solutions to my GitHub repository at <a href="https://github.com/hamatti/adventofcode-2020">hamatti/adventofcode-2020</a>. Since I'm a very beginner with Rust, I don't recommend taking them as example or you'll probably learn some bad habits.</p><p>I won't probably solve those challenges every day but rather in batches of a few days at the time.</p>
Customize your Internet experience
2020-11-25T00:00:00Z
https://hamatti.org/posts/customize-your-internet-experience/
<p>Internet is, despite its downsides, a wonderful place. One of the reasons why I love it so much as a medium, is that HTML is so editable and approachable and the way browsers are implemented, you can change a lot of things to your own taste.</p><p>I have previously written about some customizations I've done on some services that I use and decided to spend this post talking about different ways you can run your own code to change how websites and web apps look and function.</p><h2 id="the-basics-of-modifying-the-dom">The Basics of Modifying the DOM</h2><p>Before we look at different ways to run our customizations, we need to talk a bit about how to make them. When a website or web application is rendered in a browser, it is represented as <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction">a Document Object Model (DOM)</a>. It's an interface to the HTML and CSS and it allows us (and the original app creator) to modify the HTML and CSS so that we can display different things on the browser.</p><p>This won't be an exhaustive 101 tutorial for Javascript nor DOM manipulation, just a few openers to get you started.</p><h3 id="example-1-click-a-button-when-a-button-on-keyboard-is-pressed">Example #1: Click a button when a button on keyboard is pressed</h3><p>My first example is the one I find myself building most often: adding custom keyboard shortcuts to different functionalities on the website. I also like it because it's quite simple to do on any of these different ways.</p><p>Let's say that there's a button/div/span/any element that we want to click when we press left arrow key on our keyboard.</p><pre><code class="language-javascript">const toClick = document.querySelector('.buttonClass');
document.onkeyup = event => {
if(event.code === 'ArrowLeft') {
toClick.click();
}
}</code></pre><p>First we find the element by its class (here assuming there's only one element with that class; if there's more, you need bit more logic to find the correct one). Then we listen to <code>keyup</code> event and if the key being pressed was <code>ArrowLeft</code>, we trigger a click on the button.</p><p>With this base script you can add all sorts of different keyboard shortcuts by matching different elements to different key codes.</p><h3 id="example-2-add-custom-styles">Example 2: Add custom styles</h3><p>Second example is adding custom styling to specific text. Back in the day I built a Chrome extension that allowed users to specify names and then the extension would highlight their rows in tables to make it easier to find in long tables.</p><pre><code class="language-javascript">const table = document.querySelector('#longTable');
const rows = table.querySelectorAll('tr');
rows.forEach(row => {
let cells = row.querySelectorAll('td');
cells.forEach(cell => {
if(cell.innerHTML.includes('Juhis')) {
row.style = 'background-color: yellow';
}
})
});</code></pre><p>Here we go through all the cells in a given table and if one of them contains the string <code>"Juhis"</code>, we apply yellow background to the entire row.</p><h2 id="way-1-bookmarklets">Way 1: Bookmarklets</h2><p><a href="https://hamatti.org/posts/added-keyboard-support-to-on24-with-bookmarklet/">Earlier I wrote about a bookmarklet I built to add keyboard support to ON24 platform</a>. To me, bookmarklets are a great and quick way to build something I only need occasionally like was the case with ON24: I might never give another talk on that platform but if I do, I don't have to reinvent the wheel. And since I wrote a blog post about it, I can find it quickly whenever I need it (a special benefit of writing a blog!).</p><p>The way bookmarklets work is that you can define any bookmark URL to be type of Javascript and when it's clicked, it executes that code. Since the entire application logic needs to fit into a URL, it's not the best way for more complex functionalities. In Firefox, the limit 65536 bytes.</p><p>The URL needs to be in a form of <code>javascript:(function() {})()</code> with the contents of your code inside <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE">the function body of the IIFE</a>.</p><p>The way I build bookmarklets is that I usually write the code in my normal Javascript coding environment (I use VS Code or vim depending on the mood) and then just join the lines. Since I only use bookmarklets for really simple things, I don't usually minimize the code (and I think it's nicer also for sharing the code because it's easier to read and thus see what the bookmarklet actually does).</p><p>If you do want to minimize your code, you can use tools like <a href="https://www.npmjs.com/package/uglify-js">uglify-js</a> on the command line or <a href="https://jscompress.com/">JSCompress</a> in the browser.</p><p>To share your bookmarklet on a website, just create a link with the javascript code as the <code>href</code> attribute with instructions for the user to drag'n'drop it to their browser's bookmark bar.</p><p>One great bookmarklet I ran into recently is <a href="https://replace-trending.herokuapp.com/">Replace Trending</a> that hides Twitter's often depressing trending list and replaces it with more positive messaging.</p><p>One of the limitations of bookmarklets is that they only get run once when clicked. So you cannot build automated functionality that gets loaded whenever you navigate into a website. For that, we need new tools.</p><h2 id="way-2-browser-extensions">Way 2: Browser Extensions</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/11/chrome-webstore.jpg" class="kg-image" alt="Google Chrome Web Store featuring Kanjidex Chrome app" /></figure><p>One (rather large) step forward from bookmarklets are browser extensions. It's quite likely that you are currently browsing this site with multiple ones already installed. Things like password managers and ad blockers are bigger tools that are used as extensions but you can also have smaller utility tools like <a href="https://www.fonts.ninja/">Fonts Ninja</a> that gives you easy access to see what fonts are used on a website.</p><p>Software developers also use a selection of browser extensions to enhance their workflow. Extensions like <a href="https://reactjs.org/blog/2019/08/15/new-react-devtools.html">React Dev Tools</a> or <a href="https://github.com/reduxjs/redux-devtools">Redux DevTools</a> are a must-have for anyone working with React or Redux applications.</p><p>Extensions also have a special place in my heart. I've built one to <a href="https://hamatti.org/posts/morning-coffee-projects-youtube-extension/">blur out old Youtube videos from the feed</a> as well as two to <a href="https://hamatti.org/posts/adding-keyboard-shortcuts-to-viaplay/">add fullscreen & mute keyboard functionality to Viaplay</a> and <a href="https://github.com/Hamatti/teliatv-keyboard-shortcuts">TeliaTV</a>. In my past I also built <a href="https://github.com/Hamatti/tablehockeyhiliter">a small niche tool for table hockey players</a>.</p><p>Google has decent <a href="https://developer.chrome.com/extensions/getstarted">documentation for anyone who wants to build one for Chrome</a> and Mozilla <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension">has similar for Firefox</a>.</p><p>One thing I really like about extensions is that they can either be passive (always on) or active (when clicked), you can limit the use space for certain domains and you can add configuration/settings and provide much larger functionality than you could with bookmarklets.</p><p>Once you've built a custom extension, you can publish it in the store or load it locally. For my small tools, I have mostly just built them as local tools and published the code on GitHub so others can also enjoy if they need but haven't published them in the stores.</p><h2 id="way-3-tampermonkey">Way 3: Tampermonkey</h2><p><a href="https://www.tampermonkey.net/">Tampermonkey</a> is a user script manager which allow users to write small scripts (like we've done with bookmarklets) and then run them on specific websites. Rather than running individual bookmarklets, you can gather together multiple scripts (written by others as well as yourself) into one place and configure and run them with Tampermonkey.</p><p>It also comes with a built-in editor so you can write your scripts directly inside it.</p><p>I have not used Tampermonkey much as I prefer to work with either bookmarklets or extensions but back in the day Greasemonkey (which is similar but only available for Firefox) was everywhere among the Internet user base. These types of user scripts are often popular with people who are technically savvy but not developers so they can find and install scripts they need without having to make them themselves.</p><h2 id="way-4-running-a-script-in-the-console">Way 4: Running a script in the console</h2><p>I left this as the last one because it's not a very robust but it's something I do when I just need to one-off something. Instead of building the Javascript functionality into bookmarklets, extensions or user scripts, you can run the code directly in the browser's dev tools JS console.</p><p>To learn how to access the developer tools, see docs for <a href="https://developer.mozilla.org/en-US/docs/Tools">Firefox</a> and <a href="https://developers.google.com/web/tools/chrome-devtools">Chrome</a>.</p><p>The downside is that you have to store the code somewhere on your computer (or in your blog!) and copy-paste it every time you need it. The upside is that it's super flexible and you can customize the code to be run every time you run it.</p>
Added keyboard support to ON24 with bookmarklet
2020-11-18T00:00:00Z
https://hamatti.org/posts/added-keyboard-support-to-on24-with-bookmarklet/
<p>Bookmarklets are fantastic. Not having a keyboard support for a web app is less fantastic. Here's a short story and bookmarklet of how I added keyboard (and by association, presentation clicker) support to ON24's Webinar platform.</p><p>Two weeks ago I was preparing to give a talk at sthml.js meetup remotely from my home studio (I call it a studio: it's a webcam on tripod + wireless mic) and we were using ON24 as a webinar platform for that meetup.</p><p>During our rehearsal session the day before the meetup, I noticed that there was no keyboard support for switching slides which also meant that my Targus presentation clicker (that emulates(?) keyboard) didn't work either. I had to move my mouse over a small arrow and click that – not a fun thing to do when I'm standing in front of a camera and talking to an audience across the pond.</p><p>As someone <a href="https://hamatti.org/posts/adding-keyboard-shortcuts-to-viaplay/">who likes to hack things to build the functionality I need</a>, I figured I can do this by myself. Instead of building a Chrome extension for this that I knew I'd maybe need that one time instead of constantly, I went to the bookmarklet route.</p><p><a href="https://en.wikipedia.org/wiki/Bookmarklet">Bookmarklets</a> are small Javascript scripts that are triggered when a user clicks on a bookmark that contains that code. Since all I needed here was to translate some key presses into Javascript click events, I figured bookmarklet is the simplest way.</p><p>Here's the full code. I started by adding functionality for left/right arrow keys but then learned that my clicker actually sends PageUp and PageDown presses so I added them in.</p><pre><code class="language-javascript">const prev = document.querySelector('.arrow-btn.prev')
const next = document.querySelector('.arrow-btn.next')
const LEFT_ARROW = "ArrowLeft"
const RIGHT_ARROW = "ArrowRight"
const PAGE_DOWN = 'PageDown'
const PAGE_UP = 'PageUp'
document.onkeyup = event => {
if(event.code === LEFT_ARROW || event.code === PAGE_UP) {
prev.click();
} else if (event.code === RIGHT_ARROW || event.code === PAGE_DOWN) {
next.click();
}
}</code></pre><p>In bookmarklet, it looks like this:</p><pre><code class="language-javascript">javascript:void(function() { const prev = document.querySelector('.arrow-btn.prev'); const next = document.querySelector('.arrow-btn.next'); const LEFT_ARROW = "ArrowLeft"; const RIGHT_ARROW = "ArrowRight"; const PAGE_DOWN = 'PageDown'; const PAGE_UP = 'PageUp'; document.onkeyup = event => { if(event.code === LEFT_ARROW || event.code === PAGE_UP) { prev.click(); } else if (event.code === RIGHT_ARROW || event.code === PAGE_DOWN) { next.click(); } }})()</code></pre><p>To create a bookmarklet, you can drag <a href="javascript:void(function()%20%7B%20const%20prev%20=%20document.querySelector('.arrow-btn.prev');%20const%20next%20=%20document.querySelector('.arrow-btn.next');%20const%20LEFT_ARROW%20=%20%22ArrowLeft%22;%20const%20RIGHT_ARROW%20=%20%22ArrowRight%22;%20const%20PAGE_DOWN%20=%20'PageDown';%20const%20PAGE_UP%20=%20'PageUp';%20document.onkeyup%20=%20event%20=%3E%20%7B%20if(event.code%20===%20LEFT_ARROW%20||%20event.code%20===%20PAGE_UP)%20%7B%20prev.click();%20%7D%20else%20if%20(event.code%20===%20RIGHT_ARROW%20||%20event.code%20===%20PAGE_DOWN)%20%7B%20next.click();%20%7D%20%7D%7D)()">ON24 KEYBOARD SUPPORT</a> to your bookmark bar. Then you navigate to your presenter mode in ON24 and click the bookmarklet.</p><p>Voilà, you have keyboard and clicker support.</p>
Functions 101
2020-11-11T00:00:00Z
https://hamatti.org/posts/functions-101/
<p><em>This blog post is aimed for beginners. If you have already built some software, it's very likely you won't get much out of it but you're welcome to stay for the ride. Just so you know.</em></p><p>There is a brief moment in new software developer's life when the concept of functions (or subroutines or methods as they are called in some languages) is bit fuzzy. This post aims to be a good starting point on your journey to understand functions, how they are built and how they are used.</p><p>I will be using Javascript as an example language but most of this applies to other languages as well. </p><h2 id="anatomy-of-a-function">Anatomy of a function</h2><p>Let's start by looking at an example function:</p><pre><code class="language-javascript">function calculateAverage(x, y, z) {
const sum = x + y + z;
const average = sum / 3.0;
return average;
}</code></pre><p>Here we have a function called <code>calculateAverage</code> that calculates the average of three numbers.</p><p><code>calculateAverage</code> is the <em>name</em> of the function and it takes three <em>parameters </em>or <em>arguments </em>(called <code>x</code>, <code>y</code> and <code>z</code>).</p><p>The name of the function as well as the names of the parameters are up to you as a developer to decide when creating a function. However, it's not completely arbitrary what you name them as they should be named to be as descriptive as possible for people reading it. For the computer, it doesn't matter if your function is called <code>abc</code> or <code>calculateAverage</code> but for humans reading your code and calling the function, it does.</p><p>A function can have 0, 1 or multiple parameters.</p><p>Inside <code>{</code> and <code>}</code> is what is called the <em>body </em>of the function. It's the code that gets executed when the function is being <em>called </em>(well get to that later)<em>. </em>The body can contain multiple lines and it can call other functions inside it.</p><p>Optionally, a function can <em>return</em> a value. In Javascript (like many other languages), it's done by using the keyword <code>return</code> followed by the value being returned. We'll see bit later what it means to return a value.</p><h2 id="using-functions">Using functions</h2><p>There are three steps when using functions and this is something that I often see beginners being very confused with, so let's walk through step by step:</p><h3 id="defining-a-function">Defining a function</h3><pre><code class="language-javascript">function calculateAverage(x, y, z) {
const sum = x + y + z;
const average = sum / 3.0;
return average;
}</code></pre><p>We start by <em>defining</em> a function. This piece of code doesn't effectively do anything yet. It just creates a function into our software that we can use later in other parts. For example, if you find a function like this from a tutorial or as an answer to a question, simply copy-pasting it into your codebase doesn't do anything on its own.</p><p>Some functions are defined by the coder themselves, some come from <em>libraries</em> (pieces of code other developers have written and shared so you can use them) and some from the <em>standard library</em> of the language (meaning functions that are built-in to the programming language of your choice). All of them function similarly.</p><h3 id="calling-a-function">Calling a function</h3><p>A function is <em>called</em> by writing its name and passing any parameters inside parentheses.</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">calculateAverage(1, 7, 25)</code></pre><!--kg-card-end: html--><p>With this line of code, we tell our software to run the contents of the function with values <code>1</code>, <code>7</code> and <code>25</code> as parameters. What happens then is that the computer will run the function, calculate the average and return it. However, after that, the computer simply discards the return value and continues like nothing happened.</p><h3 id="a-side-step-what-is-a-scope">A side step: what is a scope?</h3><p>To go deeper into this issue, we need to talk briefly about <em>scope. </em>Scope in plain English is the <em>visibility area of a variable or function. </em>In practice, it means where a variable or function is available to other pieces of code to run.</p><p>Let me demonstrate this with an example:</p><pre><code class="language-javascript">const number = 42;
function calculateAverage(x, y, z) {
const sum = x + y + z;
const average = sum / 3.0;
return average;
}
calculateAverage(1, 7, 25);
console.log(average);</code></pre><p>Here we have one function (<code>calculateAverage</code>), three variables (<code>number</code>, <code>sum</code> and <code>average</code>) and three parameters (<code>x</code>, <code>y</code> and <code>z</code>). They have different <em>scopes</em>, i.e. they can be read in different places.</p><p>Here, <code>number</code> is available everywhere. It's defined on the <em>top-level </em>and it's scope is this entire script. Function <code>calculateAverage</code> has the same scope as <code>number</code> since they are defined at the same level.</p><p>Parameters <code>x</code>, <code>y</code> and <code>z</code> and variables <code>sum</code> and <code>average</code> are only available inside the <code>{</code> and <code>}</code> – or as well learned earlier: body of the function. This means that even though the code ran and calculated the average of those three numbers into a variable <code>average</code>, that is not available at the last line. Instead, you'll get an error <code>ReferenceError: average is not defined</code>.</p><p>Understanding the scope is crucial for a software developer. It's not the easiest though since different programming languages define scope differently so you need to learn it for each one separately. But once you grasp it, it'll stay with you for the rest of your coding career.</p><h3 id="capturing-the-value">Capturing the value</h3><p>Third step of calling a function is to capture the value into a variable.</p><pre><code class="language-javascript">function calculateAverage(x, y, z) {
const sum = x + y + z;
const average = sum / 3.0;
return average;
}
const average = calculateAverage(1, 7, 25);
console.log(average);</code></pre><p>On line 8 in the above code, we changed it to <code>const average = calculateAverage(1, 7, 25);</code> and now the result of our function call is stored in a local variable called <code>average</code>.</p><p>Please note that this <code>average</code> is different from the <code>average</code> on the line 5. You cannot have the two variables with the same name in the same scope but here, line 5 <code>average</code> only exists within that scope.</p><p>We could call the <code>average</code> on line 8 anything. It's not tied in any way to the <code>average</code> we calculated in line 5.</p><p>For us to be able to capture these values, the function needs a <code>return</code> statement. If we don't have a <code>return</code>, a function always returns <code>undefined</code> in Javascript. However in some other languages, they might return other things: Python returns <code>None</code> and Ruby actually doesn't need to have <code>return</code> at all, it will always return the last expression of the function.</p><h2 id="common-mistakes">Common mistakes</h2><p>Some common mistakes I see beginner programmers do are:</p><ol><li>Not realizing a difference between <em>defining </em>a function and <em>calling</em> a function. Just defining it with <code>function hello()</code> does not run it, you need to explicitly call it.</li><li>Not understanding the scope of variables and then trying to access variables created inside a function scope rather than returning the desired value.</li><li>Not remembering to store the output of a function into a variable.</li></ol><p>A word of encouragement if you're in this phase as a developer: it will pass. This is something you need to learn once and after that, you got it.</p><h2 id="wrap-up">Wrap up</h2><p>First you <em>define</em> a function by telling the computer what this function will do. Then you <em>call </em>it to actually run it with real values. Finally, you <em>capture the output</em> into a variable so it's not lost.</p><p>There are other advanced things you can do with functions but they are not important right now. I have left them out for now so you won't get confused.</p>
Coaching at codebar has given me a lot (via codebar blog)
2020-11-06T00:00:00Z
https://hamatti.org/posts/external-coaching-at-codebar/
<p>Over the past 8 years, I’ve also coached, mentored, and taught in various other workshops and as a software developer, I’ve gotten a lot out of it and I wanted to share those with the world. Codebar has provided a great platform for people like me to help new people get into technology industry and are doing a wonderful work in making the industry more diverse and inclusive.</p>
<p>Read the entire blog at <a href="https://medium.com/the-codelog/coaching-at-codebar-has-given-me-a-lot-81b58664492">codebar blog</a></p>
Validating dynamic data conditionally with Joi
2020-11-04T00:00:00Z
https://hamatti.org/posts/validating-dynamic-data-conditionally-with-joi/
<p>I ran into an interesting case of <a href="https://joi.dev/">data validation with Joi</a> last week and since I couldn't find a clear answer on the Internet, I figured I'd write down my exploration and a working example.</p><h2 id="the-problem">The problem</h2><p>A friend asked help with a problem of creating a validation schema. They had an object with unknown/dynamic keys and string values (see example below). For all these values, they wanted to validate according to these rules:</p><ol><li>If the value is a string that converts into a NaN when trying to convert to number, accept any string</li><li>If the value is a string that converts into a valid number, accept only numbers that are positive or zero (>= 0).</li></ol><p>Doing this type of conditional validation can be a bit tricky with Joi. There are many ways to do different conditionals but some only work in certain cases.</p><p>Let's take a look at an example data:</p><pre><code class="language-json">{
"name": "Sherlock Holmes",
"address": "221B Baker Street",
"successRatio": "0.9",
"age": "34"
}</code></pre><p>Here, we want to pass name and address to always pass but want <code>successRatio</code> and <code>age</code> to be validated and to be positive or zero.</p><h2 id="schema">Schema</h2><p>After tinkering a bit in the <a href="https://joi.dev/tester/">Joi.dev Sandbox</a>, I eventually ended up with this:</p><pre><code class="language-javascript">Joi
.object()
.pattern(/./,
Joi.alternatives().conditional(
Joi.number(),
{
then: Joi.number().min(0),
otherwise: Joi.string()
}
)
)</code></pre><p>For every value, it runs a <code>conditional</code>. The first argument of a conditional is a test: in this case we check if the value <a href="https://joi.dev/api/?v=17.3.0#number">is numeric (or converts to a numeric value)</a>. The second argument is an options object with two values: <code>then</code> and <code>otherwise</code> which are basically if/else blocks. For values that are numeric, we check that they are at least 0 and for values that are not numeric, we just check that they are strings.</p><p>With this schema and the above example data, it should pass. If you wanna see it fail, change <code>successRate</code> to a negative value.</p><h2 id="further-reading">Further reading</h2><p>If you're new to Joi, I can recommend this blog post by a former colleague of mine: <a href="https://futurice.com/blog/what-ive-learned-validating-with-joi">What I've Learned Validating with Joi</a>.</p><p>Joi's own <a href="https://joi.dev/api/?v=17.3.0">API documentation</a> is also a great place to start.</p>
How to ask help for technical problems?
2020-10-28T00:00:00Z
https://hamatti.org/posts/how-to-ask-help-for-technical-problems/
<p>
Over the years, I've been involved in a lot of different groups and
communities where people ask for help for technical problems. I've seen some
great questions and some where even just getting to the actual problem has
taken a long time.
</p>
<p>
In this post, I want to give some tips for anyone who wants to get help with
their problems because the better your question covers different things and
explains the problem and what you've done, easier it is for someone to help
you. I do want to emphasise that for beginners, it's not easy to ask questions
because there's so many unknowns. Hopefully these tips will help you on your
journey to seek knowledge.
</p>
<p>
Most of these are meant for <em>asynchronous communication </em>like chat apps
or forums or sites like Stack Overflow and focuses on programming related
questions to narrow the scope of the post.
</p>
<p>
The common thread in all these is to provide as much information and being as
specific as possible up front so you and your helpers don't have to spend a
day going back and forth before getting into the actual problem.
</p>
<h2 id="1-ask-your-question-don-t-ask-if-someone-can-help">
1. Ask your question, don't ask if someone can help
</h2>
<p>
Let's start with the first message you send. I often see people start by
asking <em>"Is there anyone who can help me with XYZ?"</em>.
</p>
<p>
It sounds reasonable, right? However, it can hinder your ability to gain help
a lot:
</p>
<ul>
<li>It's hard for the helper to know if they can help</li>
<li>It lengthens the discussion loop unnecessarily long</li>
</ul>
<p>
Let's say you are struggling with something in CSS. If you post a message on a
proper chat room (like developer communities in Discord or Slack), there might
be a lot of people who know CSS but they might not know the exact thing you're
struggling with.
</p>
<p>
Second, by asking that requires someone to say "Yes, what's your problem?" and
it may take a day to even get into the discussion of the problem itself.
</p>
<p>
Instead, just ask your question directly. "I'm trying to center align a
<code>div</code> inside another <code>div</code> but can't figure out how?" is
a much better start. We'll learn how to make it even better later in this
post.
</p>
<p>
When you ask the question like that, anyone at any point can take a look at it
and have a better idea about if they can help you and they can start answering
right away. You might get a solution to your problem even before you return to
read the chat for the next time.
</p>
<h2 id="2-explain-what-you-are-trying-to-achieve">
2. Explain what you are trying to achieve
</h2>
<p>
It might sound obvious but very often it's missing and can lead the people
helping into a wild goose hunt to the wrong direction. This is especially
relevant with new developers because you might be trying to do something with
a bit wrong set of technical tools.
</p>
<p>
The more specific you can be here also helps people grasp what you're aiming
to do which helps them give you better answers.
</p>
<p>
Rather than focusing on the technical implementation details, focus on what
you want the effect of the code to be.
</p>
<p>
This is also known as
<a href="https://en.wikipedia.org/wiki/XY_problem">XY Problem</a>.
</p>
<h2 id="3-include-example-input-and-output-data">
3. Include example input and output data
</h2>
<p>
If you're working on something that takes in data and outputs something else,
provide both an example set of data to be used as an input
<strong>and</strong> an expected output. Even better if you can provide
multiple sets of different input/output pairs so your helper can test out that
their implementation of the fix actually answers to your question.
</p>
<p>
For example, if you're working on
<a href="https://joi.dev/">some regex or validation with Joi</a>, offering a
test input helps a long way in reducing back-and-forth between you and your
helper.
</p>
<h2 id="4-share-the-errors">4. Share the errors</h2>
<p>
<em>"My code doesn't work, why?" </em>isn't very helpful but often encountered
question. The answer to that often is <em>"I have no idea".</em>
</p>
<p>
So instead of being vague, be as specific as possible. One way to be specific
is to share the error code you get (and share it every time when it changes
during your process of figuring it out). For an experienced developer, errors
reveal a lot so sometimes just looking at the error alone we might be able to
figure out the problem – especially if it's a very common one.
</p>
<p>
And please, always share those as copy-paste into code blocks (see next
section for those) because that allows us to copy-paste further and search
from Google if it's something we're unfamiliar with. Screenshots of errors are
often just one more hinderance in trying to figure things out.
</p>
<p>
If you're using some specific applications or libraries, share version numbers
of those. For example, if you have a problem with Django, please tell the
version of Django and version of Python that you're running. Programming
languages and tools evolve and it might be that it's a specific issue to your
version.
</p>
<h2 id="5-show-your-code">5. Show your code</h2>
<p>
It's very important to show what you have tried to do for a couple of reasons.
</p>
<p>
First, it's more motivating for someone to help you when they know you're not
just asking someone to do the work for you but that you've actually tried
solving the issue yourself and are genuinely stuck.
</p>
<p>
As someone who teaches a lot, I try to avoid giving direct answers as much as
possible and rather try to help people learn how to figure it out themselves
and how to find the right information. That's impossible if you're only
looking for direct complete answers.
</p>
<p>
Secondly, it can be really difficult to give programming advice to a specific
problem if the person giving advice doesn't know what your code looks like.
This is especially important when you have an error or something's functioning
incorrectly.
</p>
<p>
There are different ways to share the code. For HTML/CSS/Javascript stuff, you
can use <a href="https://codepen.io/">Codepen</a> or for more complex frontend
code, use <a href="https://codesandbox.io/">Codesandbox</a>. The big benefit
of these two is that you can run the code and see the result. Most problems
are not solved by staring at the code and returning the answer but often
require some tinkering and these platforms are great for that.
</p>
<p>
For other code, there are tools like
<a href="https://pastebin.com/">Pastebin</a> which allow you to paste
anything, choose a syntax highlight and click submit. Or if your code is
short, you can send it directly in Discord/Slack. For that, here are couple of
tricks to make it easier for your helper to help you:
</p>
<p>To share code in these platforms, wrap it around three backticks:</p>
<pre class="language-bash"><code>```
your code here
```</code></pre>
<p>
On Discord, you can also add a language definition after the first three ticks
to add syntax highlighting:
</p>
<pre class="language-bash"><code>```js
// javascript code here gets highlighted
```</code></pre>
<p>
On Slack, you can create a code snippet from the lightning menu which allows
you to add syntax highlighting:
</p>
<figure class="kg-card kg-image-card kg-card-hascaption">
<img src="https://hamatti.org/assets/img/ghost//2020/10/slack-code-snippet.jpg" class="kg-image" alt="Slack's Code Snippet modal with example Python code" />
<figcaption>(thanks Juri for pointing this feature out)</figcaption>
</figure>
<p>
Reading code that is not shared inside these code blocks (created by triple
backticks) is very difficult to read.
</p>
<h2 id="6-create-a-minimal-complete-reproducible-example">
6. Create a Minimal, Complete, Reproducible Example
</h2>
<p>
Codebases are often large and complex and it's not beneficial to share all
your code that exists.
</p>
<p>
Quoting from
<a href="https://stackoverflow.com/help/minimal-reproducible-example">a great instruction post in Stack Overflow</a>:
</p>
<blockquote>
Your code examples should be…<br /><br />…Minimal – Use as little code as
possible that still produces the same problem<br /><br />…Complete – Provide
all parts someone else needs to reproduce your problem in the question
itself<br /><br />…Reproducible – Test the code you're about to provide to
make sure it reproduces the problem
</blockquote>
<p>
I won't copy or rephrase everything from that post but rather advice you to
read it with thought.
</p>
<p>
This is one of those things where I think we need to be most lenient,
especially with new developers. If you're new to software development and your
code doesn't work, it's very likely you don't know why (that's why you're
asking, after all) and it can be very difficult to create an example like
that.
</p>
<p>
But reading it through and understanding why it's a positive thing to thrive
towards can help you a long way.
</p>
<h2 id="wrap-up">Wrap-up</h2>
<p>
A good question is a combination of all the above: asking directly to get
things started, stating what you're aiming to achieve, sharing the errors
you're encountering, sharing the code is causing you these problems and making
the shared examples minimal, complete and reproducible.
</p>
<p>
By putting these advice into work, I can guarantee that your experience with
seeking help will get much better. A side effect might be that you get less
discussion going (less people asking for more info) but you also save your and
other people's time when they don't have to ask a lot of things only to find
out that they cannot help you that time.
</p>
<p>
And finally, remember to say thanks when you get help. I didn't add it as its
own point since I've never had the issue of people not saying thanks. But if
you have gotten help and didn't say thanks – do so in the future.
</p>
<p>
I know asking help can be a daunting task: exposing your imperfection to
strangers can be scary. Despite making this long list of things you should do,
I do want to encourage you to ask even if you are not perfect in all of these.
This post is aimed mainly to provide you with some new ideas that you might
not have thought about or known about.
</p>
<p>I wish you get help for your technical problems!</p>
<p>
<em>(PS. If you're an experienced developer, you might notice that all of these
translate very nicely into writing good bug reports. That's because a bug
report is essentially asking for help in fixing something.)</em>
</p>
<h2 id="additional-reading">Additional reading</h2>
<ul>
<li>
<a href="https://hamatti.org/guides/humane-guide-to-debugging/">Humane Guide to Debugging Javascript by me</a>
</li>
<li>
<a href="https://hamatti.org/guides/humane-guide-to-python-errors/">Humane Guide to Python Errors by me</a>
</li>
<li>
<a href="https://www.chiark.greenend.org.uk/~sgtatham/bugs.html">How to Report Bugs Effectively by Simon Tatham</a>
(thanks Aarni for sharing this)
</li>
<li>
<a href="https://twitter.com/b0rk/status/1546875361002135554">debugging strategy: write a message asking for help by Julia Evans
(twitter)</a>
</li>
<li>
<a href="https://kittygiraudel.com/2022/07/15/a-guide-to-asking-for-help/">Help Me Help You: A Guide to Asking for Help by Kitty Giraudel</a>
</li>
<li>
<a href="https://angelika.me/2024/01/03/how-to-ask-for-help-with-your-code-online/">How to ask for help with your code online by Angelika Tyborska</a>
</li>
</ul>
It's time for a remote work retrospective
2020-10-21T00:00:00Z
https://hamatti.org/posts/its-time-for-a-remote-work-retrospective/
<p>It's been over six months since many of us, especially us working in the information or creative industries, were thrown into a forced remote work situation. For some, it was a welcomed thing and for some, it's been a struggle.</p><p>I have been working in a beautiful on-site/remote hybrid mode for years already: I work where my work can be best done. Sometimes it means with people in an office, sometimes it's remote meetings, sometimes deep work at home or in a pub. Personally for me, that's the optimal situation: the work defines where and how it's done. </p><p>As a remote work enthusiast, I want to highlight something to start this with: <strong>our current situation is not normal remote work.</strong> There's a pandemic and us being stuck at home for 24 hours a day with limited social interaction is not a good representation of remote work in normal situation.</p><p>But what I really wanted to talk about today is <strong>Remote Work Retrospectives. </strong>I like retrospectives in general, I think they are a great way to improve your work and processes and how your team operates. And I think now that we're getting used to this situation and it's not going away any time soon, it's a perfect time to take some time both alone and with your team to consider if you are making the best out of the situation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2020/10/remote-work-cafe.jpg" class="kg-image" alt="Man wearing a mask sitting in a cafe with a laptop" /><figcaption>Photo by <a href="https://unsplash.com/@plhnk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Paul Hanaoka</a> on <a href="https://unsplash.com/@plhnk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></figcaption></figure><h2 id="personal-retrospective">Personal Retrospective</h2><p>Working remotely <em>can </em>be just like working from the office: sticking to 9-5, communicating with colleagues as usual (although via tools like Slack or Teams and meetings with video calls rather than in-person) and so on. But it's worth taking a look at what remote work can really offer and if your current way of working is best for you and the situation.</p><p>My favorite way of thinking about these kind of things is this: <strong>imagine a perfect work day or week.</strong> I do it on very detailed level: when would I wake up, what my morning routines would be, how would I like to splice my work and what kind of work things I'd like to focus on and when. Rather than starting from the real-life restrictions and limitations, start from the best possible. </p><p>For example, when I was doing full-time software development, I really liked to split my day into two intensive high-productive sections: few hours in the morning followed by a long break. 2-2.5hrs of lunch, taking a walk, playing video games, getting my mind away from work. Then after the break, another intensive session of work. It helped me feel better and be more productive with the time I used for work.</p><p><strong>What are you struggling with? </strong>is another good question to ask yourself. Transforming from fully on-site into a fully remote during a pandemic is not an easy thing. It can be even harder than starting remote from scratch because there are so many things we are doing that are built for being in office and in-person interactions that can be hard to translate into remote. </p><p>But there are also other things that changed. For example, many have been happy that they don't have to commute 30 minutes or an hour one way each day but it can also be difficult to get into the work mood or get away from it when starting and finishing your day. If you struggle with that, it doesn't mean that you're bad at working remotely: it's just that the change happened fast and you need to come up with ways to counter that. Some people go for a walk around the block to mimic commute.</p><p>Or maybe it's the environment you work in that could be improved: are you working from your kitchen table when you were used to multiple screens and a nice chair at work? Or maybe you live with someone in a small apartment and it's hard to focus or do video conferences when you can't be in private. </p><p>During our busy weeks of surviving the new situation, we might not always make time to think about these things. But now is a good time to do so. </p><p>It's not all grim and gnarly though. <strong>What are the things you enjoy about remote?</strong> Is it the fact that you can take your dog for a walk during the day or that you can spend the saved commute time eating breakfast and reading a book? Maybe you can focus much better when working from home than busy office? Or the fact that it's so easy to do laundry during the day? Write those down as well and make sure you build your ways of working with the positives.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2020/10/remote-meeting.jpg" class="kg-image" alt="A video conference on a laptop and a maroon coffee mug next to laptop" /><figcaption>Photo by <a href="https://unsplash.com/@cwmonty?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Chris Montgomery</a> on <a href="https://unsplash.com/@cwmonty?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></figcaption></figure><h2 id="team-retrospective">Team Retrospective</h2><p>If our personal lives have been under turmoil, a lot has changed for the teamwork too. From sitting in the same room with colleagues and eating lunch together to not maybe even seeing them in 7 months other than via a screen can be a daunting change.</p><p>It's very common that the level of communication drops or feeling of belonging to a group decreases in a situation like this. And that's why it's important to think about things from the team perspective.</p><p>Gather your team together for a team retro. This forced new reality can be a good opportunity for us to critically think about the ways of the working within the team. </p><p><strong>What's the best way for us to communicate with each other? </strong>There's no 1-to-1 replacement for asking a colleague something in the office. You can use chat tools but it's always a bit different. Some people might have turned off all the notifications because there's too many of them in bigger organizations.</p><p>If you're a developer and feel the productivity is dropping due to communication delays and issues, you could experiment with <a href="https://www.remotemobprogramming.org/">pair-programming or mob programming</a>. Maybe you could spend a day of the week doing a synced session like that working together. </p><p>On the other hand, if the constant communication and distraction is an issue, maybe try to adopt a bit slower asynchronous communication style. Basecamp is a company that's operating fully remotely and <a href="https://basecamp.com/guides/how-we-communicate">has written a lot about the way they communicate</a>.</p><p>The key is to look into what you've been doing now, what's working and what isn't. And to bravely experiment with new ways because remote work does require a different mindset than onsite work to be most beneficial to everyone.</p><p>Also talk about the social aspect of being in a team. Some people might feel lonely while others enjoy not having to socialize at work that much. The only people who know the situation in your team is your team. Encourage open discussion about these topics.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://hamatti.org/assets/img/ghost//2020/10/clapper.jpg" class="kg-image" alt="A film clap with desert on the background" /><figcaption>Photo by <a href="https://unsplash.com/@jakobowens1?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Jakob Owens</a> on <a href="https://unsplash.com/s/photos/action?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></figcaption></figure><h2 id="call-to-action">Call to action</h2><p>A good retrospective is not just a discussion of how people are feeling. A good retrospective also includes some actions that the team decides to take. Don't over do it though: making a list of 10 things to improve leads to nothing happening because there's still all the regular work to be done.</p><p>Start with one experiment or improvement that your team agrees on and finds most important. Decide who in the team has the ownership of that action and make sure the team buys into the improvement and that it's the type of thing that the team can improve on (rather than needing lot of approval from upper management or other external parties).</p><p>Implement, test and talk about it in the next retro to see if it makes sense to continue.</p><h2 id="conclusion">Conclusion</h2><p>Remote work can offer a lot of great things. I'm a big believer in a kind of a hybrid way of working: I like to match my environment with the work that needs to be done. If the work being done doesn't benefit from me being in the office, I don't go to the office to do it and vice versa.</p><p>During this pandemic, we don't have a lot of options but what we learn from remote work now (and whether we like it or not on a personal level) can transform the future of work in a big way once the situation clears and we do have an option.</p><p>So if you are in a situation where you can spare a bit of time and energy at work to look at your ways of working both on a personal level and on a team level, I highly recommend doing a retro on this remote situation.</p>
Dev Diary #2: Working prototype
2020-10-18T00:00:00Z
https://hamatti.org/posts/dev-diary-2-working-prototype/
<p>In May, I <a href="https://hamatti.org/posts/dev-diary-1-pokemon-tcg-cube-draft/">started working on a side project</a> building an online draft tool for Pokemon TCG. I had a clear idea of what I wanted from the basic functionality but the world situation hasn't been easy so my work on the project was small pieces of frustration here and there. Over the summer, I sat down a couple of times trying to make this happen but struggled to get the technical side figured out.</p><p>Today, I finally have a working prototype! I didn't make big changes to the technical approach compared to my failed attempts but maybe my subconscious had been working on the problem over the summer since this time it clicked.</p><h2 id="the-prototype">The Prototype</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/10/prototype-view.png" class="kg-image" alt="Draft view with 6 cards in the center and list of picked cards on the right" /></figure><p>I have to say, I'm so excited by the flow of the app from the user perspective. It's intuitive to use: after joining a game (currently only supports one hardcoded test session), you get a list of cards to pick from and once you make your pick, you get the rest of the cards from the player next to you in the rotation.</p><p>On the right side, you see a barebones list of the cards you have picked up. The picks list is definitely going to under go a major overhaul and it's a piece I've got most feedback on: people want a way to organize their cards to their liking and see the exact info of the card to make sure they can make optimal picks for the future.</p><h2 id="demo">Demo</h2><figure class="kg-card kg-embed-card"><iframe width="480" height="270" src="https://www.youtube.com/embed/Oi8wW1HvIBc?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></figure><p>Above is a quick demo video with two players picking cards. Manual testing of a real-time multi-user applications has proven to be quite a challenge. Not necessarily <em>difficult</em> but <em>time consuming</em> since you have to make all the clicks and decisions for each user. I can't wait to get the second version of the prototype out in a way that I can deploy it to a server and run some tests with people other than myself.</p><p>Improving something that runs and works is so much easier and motivating than staring an empty project. I think I was in the empty project state for way too long but now I can start making small tasks to implement new things or improve existing which makes it easier to work on it shorter time periods at the time.</p><h2 id="show-us-what-s-under-the-hood-">Show us what's under the hood!</h2><p>Since you're here on my blog, you're probably interested in learning about the tech side of the project as well.</p><p>On the backend, I run node with express. Currently the project doesn't have any kind of persistence (card pools are hard coded for example) but I'm planning to implement Postgres database for user-created cubes and games and either using that or some no-SQL approach for logging draft actions. One of the down-the-road things I'm excited about is analysing picks: as games are played, we'll get a lot of data of what picks players make on specific pools of cards and that can make a lot of interesting insights into different cubes and even individual boosters.</p><p>On the frontend, I'm using React. Nothing fancy or special here, just basic React stuff. I might add some state management like Redux to make it easier as there's a lot of state all over the place and it's becoming a bit hard to manage.</p><p>To connect the two (and users to each other), I use <a href="https://socket.io/">Socket.io</a> which is a great Javascript library for building websocket-based applications. Most of my struggles have been making the sockets do what I want to do but over a lot of practice and trial and error, I've gotten quite comfortable with it.</p><h2 id="next-steps">Next steps</h2><p>There are so many things to work on and that excites me:</p><ol><li>Private server testing<br />One of the things that needs to happen next is to remove a bunch of hardcoded stuff and make it deploayble to a server. This will help me get started with private testing with players from the Pokemon cube draft community. More feedback, more bugs exposed and more ideas!</li><li>Improving stability<br />Right now, this thing breaks down really easily. Sockets are quite fragile and in this kind of thing, I need to improve the stability a lot so that small network issues or accidentally refreshing the page doesn't ruin the draft for everyone.</li><li>Organizing & managing picks<br />As I mentioned earlier, one thing people really feel a big need for is good tools for managing the list of your picks. I don't exactly yet know what kind of things I should build there but I'll probably spend a weekend in the future playing MtG Arena's draft to gain some inspiration and talk with players more.</li><li>Tooling for building cubes & games<br />A crucial first bit in the game admin side is building a way for players to create their own cubes from lists (or importing Google Sheets as they are currently used to track and draft cubes).</li></ol><p>After these four main points, the game should be in a spot where I can run controlled test drafts with the community. If you are running Pokemon TCG Cubes with your friends online and would like to test out this, send me an email at juhamattisantala ((at)) gmail ((dot)) com and we can talk!</p>
Pandemia löi multa ilmat pihalle
2020-09-10T00:00:00Z
https://hamatti.org/posts/pandemia-loi-ilmat-pihalle/
<p>Kun maaliskuussa pandemia laittoi Suomen sekaisin, multa katosi sormia napsauttamalla suurin osa siitä missä olin hyvä. Töistä katosi tapahtumat, ihmisten tapaamiset ja mun suunnittelema 6 viikon Euroopan reissu, jonka aikana oli tarkoitus vahvistaa suhdetta kollegoihin ja paikallisiin devaajayhtesöihin Ruotsissa ja Saksassa. Harrastuksista katosi meetupit, lounaat ja konferenssit. Eikä mulla hirveästi arjessa muuta ollutkaan.</p><p>Ensimmäisinä viikkoina kun tilanne iski päälle, olin vielä optimistinen. Kyllähän mä nyt aikaan saavana kaverina keksin miten muutan oman työni fyysisistä kohtaamisista ja tapahtumista digidigiin.</p><p>Sitten meni kuukausi ja kohta toinenkin ja löi tyhjää. Alkoi ahdistamaan ja stressaamaan. Pyörin yöt sängyssä, pelasin päivät pleikkaa. Kaikki uusi mitä yritti töissä tehdä kadonnutta vanhaa korvaamaan meni pieleen. Kallion yksiön seinät alkoi kaatumaan niskaan pahasti.</p><p>Kesäkuussa tuli pieni henkireikä. Pahimman tilanteen rauhoituttua karkasin maaseudulle ja vietin 5 viikon kesäloman, jossa sain vihdoin unta. Loman jälkeen 3 hyvää päivää töissä kovalla energialla ja sitten alkoi taas mennä yöt sängyssä pyöriessä, unet jäi 2-3 tuntiin per yö aamuyön pikkutunneilla ja romahdin aika lailla. Yritin puskea, istua palavereissa ja saada aikaan. En saanut.</p><p>Meinasin pyörtyä kesken yhtä presistä ja jouduin jättää pari palaveria kesken, kun en enää muistanut mitä edellinen puhuja oli just sanonut. Syksy tuntui niin toivottomalta. Ei mitään toivon kipinää mihin tarttua, ei mitään mitä suunnitella. Onneksi mulla oli silti työ ja hyvät ihmiset ympärillä sentään.</p><p>Kun en ollut kuukauteen nukkunut yhtään hyvää yötä, buukkasin palaverin mun esihenkilön kanssa ja varasin ajan psykologille 5 kerran pika-apuun. Töissä sitten sparrailtiin, että voisin ryhtyä syksyllä tekemään pienellä kaistalla konsulttihommaa jälleen, jotta olisi fiksua tekemistä ja tulisi hyödyllinen olo. Se helpotti heti, oli taas jotain toivoa syksystä.</p><p>Heti keskustelun jälkeen stressi alkoi hälvenemään. Ekana yönä en saanut ihan heti unta unirytmin sekaisin olon takia, mutta huomasin että sellainen jokaista hetkeä kalvava stressi oli poissa. Sen jälkeen oon nukkunut joka yö viimeiset 5 viikkoa hyvin.</p><p>Psykologin kanssa ollaan juteltu kaikenlaista. Siitä kuinka vaativa mä oon itselleni, kuinka huono oon itsemyötätunnossa ja kuinka mun ainoa tapa purkaa stressiä on tehdä ja kun nyt ei oo päässyt tekemään, on stressi kasaantunut.</p><p>Kun uni palasi, alkoi taas olla energiaa. Vähitellen työt alkoi sujumaan ja muutaman viikon jälkeen aloin ensimmäistä kertaa puoleen vuoteen taas innostumaan asioista. Ensi viikolla mulla on viimeinen sovituista tapaamisista psykologin kanssa.</p><p>2 viikkoa sitten päätin samaan saumaan laittaa myös sosiaalisen median tauolle. Poistin kännykästä Twitterin ja Facebookin ja blokkasin läppäriltä näiden lisäksi myös Imgurin. Tää ei suoraan liittynyt pandemiaan, mutta ajattelin kokeilla, jos tilannetta edesauttaisi se, ettei kaikkia päiviä lukisi jenkkien sisäpolitiikasta ja näkisi mitä kaikkea muut saa koko ajan aikaan. On tehnyt todella hyvää.</p><p>Siitä huolimatta, että oon pysynyt terveenä (ja lähipiiri myös!) ja oon ollut etuoikeutetussa asemassa että työt ei lähtenyt alta, pandemia iski todella kovaa ilmat pihalle. Siksi tän kirjoittaminen tuntuu samaan aikaan niin ensimmäisen maailman ongelmalta verrattuna niihin, joihin tilanne on iskenyt pahemmin. Mutta samalla myös tiedän, että moni muukin painii samanlaisten fiilisten kanssa: ei niin paha, että kehtais valittaa, mutta silti niin paha ettei unta saa. Siksi kirjoitin.</p>
The many designs of on-screen inputs
2020-08-26T00:00:00Z
https://hamatti.org/posts/many-designs-of-on-screen-inputs/
<p>Most of my readers have probably used a keyboard to fill in a form on a computer. And these days, probably you also have filled in a form with a on-screen keyboard with a mobile device like tablet or phone. And both of those are essentially the same: you replace pressing plastic keys with tapping the screen but in terms of reaching for the correct key is not inherently different.</p><p>But what happens when we remove keyboard and touch from the equation? This is daily for gamers: from arcade cabinets to modern consoles – from carving your initials to the high score to be forever remembered to filling in your long Netflix password.</p><p>I have been very frustrated with different console apps that have failed to provide a proper user experience when filling in my information to sign up or login to services. And some have provided a really great workaround experiences. In this blog post, I'd like to explore some ideas and different types of on-screen input systems.</p><h2 id="arcade-games-and-high-scores">Arcade Games and High Scores</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/roll.png" class="kg-image" alt="Retro arcade style three letter initial high score screen with letters JMS selected" /></figure><p>In the golden age of arcade gaming, you didn't have accounts to login. But you had something more important: your initials (or profanities if you were a kid) to be carved into the hall of fame of high score table. With the limitations of old arcade controls (often a joystick and couple of buttons), there had to be a way to input your initials.</p><p>There were couple of main ways: one of them being "an alphabet roll" (I tried but couldn't figure out what you would call them) where you move the joystick up or down to scroll through available letters and numbers and move right or left to select which of the three initials you want to change or.</p><p>With the latin alphabet having under 30 letters and some selection of numbers and special characters, it was doable. Just imagine sorting through 100+ options to find your own – that's very slow and thus not very usable. I assume this was also one of the reasons for short maximum lengths because it would have been a bad experience trying to type a long word or a sentence with this method.</p><h2 id="on-screen-keyboards">On-screen keyboards</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/onscreen-keyboard-2.png" class="kg-image" alt="A on-screen keyboard illustration in alphabetical order and 9 columns with selection at A" /></figure><p>The other method was something that is still very much active: a on-screen keyboard you navigate with your joystick or d-pad to select characters. This is very popular for things like character naming screens like in Pokemon Red, Legend of Zelda: Link to the Past and Undertale.</p><p>It's a rather faster method than the alphabet roll and in my experience is the main method for all sorts of text inputs in console games. But I think it's not a particularly great one when we start talking about email addresses or long passwords. And quite frankly, it's horrible for security because anyone who can see the screen can see you type your password one character at the time.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/onscreen-keyboard-1.png" class="kg-image" alt="Examples of on-screen keyboards from Pokemon Red, Link to the Past and Undertale" /></figure><p>There's a lot of options to make this layout better or worse and one of them is the amount of columns and rows. Pokemon fits 26 letters into 3 rows of 9 columns, Link to the Past does 3 rows of 10 columns and Undertale with 4 rows of 7 columns.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/wwe-universe-input.jpg" class="kg-image" alt="WWE Universe PS4 app's two-row character input screen for email" /></figure><p>WWE Universe app on PS4 uses 2 rows of 14 columns. I have never logged into that because of this. The more spread out the characters are, the more I need to move between them. They do offer some shortcuts like gmail, hotmail and yahoo email addresses but I still don't like to move around that much.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/ps-store.jpg" class="kg-image" alt="PS Store QWERTY on-screen keyboard " /></figure><p>While all the previous on-screen keyboards were in alphabetical order, PS Store on Playstation 4 offers a more familiar QWERTY keyboard and offers shortcuts to capitalization, special characters and accented letters with controller buttons.</p><p>One cool feature they offer (which works better on an idea level than in practice) is that you can switch into a mode where you can tilt your controller to control a moving crosshair to select characters. I assume that with a bit of practice, it can probably be the fastest input method but at least for me, it's still very shaky and inaccurate.</p><p>I also <a href="https://twitter.com/isfotis/status/1298604835219087360">learned from Fotis that PS4 also supports using the touchpad as a cursor </a>which sounds like a good improvement for the situation.</p><p>Regardless of the exact implementation details, all of these are still very slow to type with and I often just skip.</p><h2 id="daisy-wheel-keyboard">Daisy Wheel Keyboard</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/daisywheel-1.png" class="kg-image" alt="Daisy Wheel keyboard with ABCD pedal selected" /></figure><p>When Steam launched their Big Picture mode, they introduced a daisy wheel keyboard that peaked my interest and back then I did some testing and really liked the idea.</p><p>The idea in daisy wheel keyboard is that it groups a small amount of characters to individual pedals (like four in the example) and takes advantage of the analog stick controls to select pedals smooth and fast and then directly inputting a character with a button press.</p><p>It has a certain learning curve but once you get the idea and remember where on pedals certain characters are, it's the fastest of all of these examples to moving from one character to another. And it's a very pleasant experience because of the smoothness and speed it offers.</p><p><a href="https://github.com/likethemammal/daisywheeljs">Chris Dolphin created a Javascript version of the daisy wheel controller</a> if you want to test it out or use it in your own projects.</p><h2 id="skipping-the-keyboard">Skipping the keyboard</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/plex-1.png" class="kg-image" alt="Text: To sign in, visit url and enter the code below. A7GM" /></figure><p>Keyboards are familiar to us. Implementing an on-screen keyboard doesn't require any creative thinking nor extra design process. But they are not very fit for the use case. </p><p>A keyboard that relies on physical buttons or touch screens is fast because you don't have to traverse from one character to another but can directly move your finger to the right place. With controllers and joysticks, that experience is lost and inputting becomes a bad experience.</p><p>Some apps have taken a very different design philosophy. One of the examples for this is Plex which gives you a 4 character/digit code and instructs you to use a web browser with another device to go to plex.tv/link and input the given code. This will then automatically recognize the device that the user is logging in from and confirm the action.</p><p>It's so much more comfortable typing four characters with a keyboard into a browser window than navigate and login with email and password using an on-screen keyboard with controller.</p><h2 id="conclusion">Conclusion</h2><p>These are the four methods I've seen most often and it's very possible I'm missing out some. If that's the case, <a href="https://twitter.com/hamatti">let me know in Twitter</a>.</p><p>There's a lot of innovation space for good solutions in this area. I think we're currently forced to live with bad user experiences because apps on consoles don't seem to be a priority to anyone. I wish someone would rethink the opportunities like Steam started to do with their Big Picture mode and Daisy Wheel keyboard.</p>
Arts, crafts and software: enhancing Flamme Rouge
2020-08-19T00:00:00Z
https://hamatti.org/posts/arts-crafts-and-software-improving-flamme-rouge/
<figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/flamme-rouge-box.png" class="kg-image" alt="Flamme Rouge box" /></figure><p>I <strong>love</strong> <a href="https://boardgamegeek.com/boardgame/199478/flamme-rouge">Flamme Rouge</a>. It's a cyclist racing game for 1 to 6 players (or 12 if you're adventurous and have that many friends) that is simple in mechanics but deep in strategy. Designed by Asper Harding Granerud and published by Lautapelit.fi, it has already two expansions with third one on its way. </p><p>The upcoming expansion inspired me to buy the existing <a href="https://boardgamegeek.com/boardgame/229941/flamme-rouge-peloton">Peloton</a> and <a href="https://boardgamegeek.com/boardgameexpansion/253354/flamme-rouge-meteo/">Meteo</a> expansions and that meant my box become a cardboard storehouse full of ziplock bags, track pieces and cyclists and it was difficult to manage. Peloton also introduced a concept of bot teams that you can use to bring more teams on the track when playing solo or with one friend. Especially during these times of social distancing, anything that provides a good solo experience is exciting for me.</p><p>So I spent a few evenings crafting and coding and created a few things both digital and physical to make my life with Flamme Rouge even more enjoyable.</p><h2 id="component-insert">Component Insert</h2><p>First I wanted to solve the problem of having all the cards, track pieces and cyclists running around in the box. I had all my cards sleeved and stored inside individual small ziplock bags but it was still bit cumbersome. Everytime I wanted to play, I basically had to empty the box, everything would slide around and I had to take stuff out and put back into bags.</p><p>Luckily, custom inserts for board games are quite a popular thing and people have been building them for a long time. I found a company called Dicetroyers who build custom hdf inserts for multiple games and luckily, <a href="https://www.thedicetroyers.com/shop/organizer-flamme-rouge-peloton-meteo/">Flamme Rouge was one of them</a>.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/flamme-rouge-inserts-out.jpg" class="kg-image" alt="Assembled hdf inserts for Flamme Rouge outside the box" /></figure><p>The pieces arrived in mail a while after a while. It took me a long evening gluing them together since I hadn't used glue since 5th grade or so. But eventually, everything stuck to each other (and still is!) so I think I succeeded in the process.</p><p>There's a card holder that fits 18 decks (2 decks per cyclist plus extra slots for tracks, bots and reference cards. There's a 6-slot cyclist insert that keeps all the cyclist safe, a double-slotted holder for exhaustion cards that can be used during game play and holders for curves and straight pieces.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/flamme-rouge-inserts-in.jpg" class="kg-image" alt="Assembled hdf inserts for Flamme Rouge inside the box" /></figure><p>All of them fit nicely together and the card holders fit cards with sleeves. I use Arcane Tinmen's clear M-sized sleeves and it turned out they are slightly too long. So I spent another night cutting all my 300 sleeves a few millimeters shorter but now everything fits and the sleeves also look a bit nicer as they are tighter fit.</p><p>One downside of the 18-deck holder is that when cards are inside their slots, you cannot see what colors they are. I designed and printed an extra sticker to be placed on both ends of the container to indicate the order of the colors.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/IMG_20200817_163138__01.jpg" class="kg-image" alt="18-deck card holder with pink, white and blue cards standing upright and a colored circles at the end of the holder" /></figure><p>I'm no graphics designer but I'm pretty happy about how it turned out. The background color is bit off from the color of the wood but I'm not too picky. With this installed, I don't have to guess or double check but can just pick up the cards I want.</p><h2 id="flamme-rouge-bot-companion">Flamme Rouge Bot Companion</h2><p>Peloton expansion for the game introduced two different automated teams to allow solo play or to add some excitement for two player games. Two teams you can add to the game are <em>Peloton </em>and <em>Muscle</em> teams and they function by building decks with normal cards and extra bot cards, shuffling the deck and drawing one card each round.</p><p>My first solo game with two bot teams meant I had to navigate 5 decks: two of my own, two for muscle team and one for peloton team. That was quite a lot and bit too much for my taste so I decided to build something with my software development skills. Say hello to <strong>Flamme Rouge Bot Companion.</strong></p><p>I actually built two of them: one is a Python command-line tool and second is a web application.</p><h3 id="flamme-rouge-bot-companion-script">Flamme Rouge Bot Companion Script</h3><p><em>You can find the code at <a href="https://github.com/Hamatti/flamme-rouge-bot-companion-script">hamatti/flamme-rouge-bot-companion-script in GitHub</a>.</em></p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/script-screenshot.png" class="kg-image" alt="Text output of Flamme Rouge Bot Companion cli tool" /></figure><p>I started with a command-line script because it was faster to build and I knew I could use most of the logic in the web app too. The script is quite barebones: it asks you how many teams you want and each press of ENTER will show next round's cards.</p><p>It works great and does what it needs to do but I quickly realized that it took quite a lot of reading and it was rather error prone.</p><p>Hence, I decided to take the logic and build a graphical web application that I thought might also be more useful for others.</p><h3 id="flamme-rouge-bot-companion-web-app">Flamme Rouge Bot Companion web app</h3><p><em>You can use it at <a href="https://flamme-rouge-bot-companion.netlify.app/">https://flamme-rouge-bot-companion.netlify.app/</a> and find the code in <a href="https://github.com/Hamatti/flamme-rouge-bot-companion">hamatti/flamme-rouge-bot-companion in GitHub</a>.</em></p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/fr-webapp-screenshot.png" class="kg-image" alt="Flamme Rouge Bot Companion web app showing cards 4, 5, 5, 2 and 3" /></figure><p>The web app is still graphically quite simple. Partly because I'm no designer and partly because I wanted to play a few games with it to make sure the flow is good before going into all the details.</p><p>One thing I'd love to add are scans of the actual cards but since I don't obviously own the rights to those, for the public version, I created simplistic, easy-to-read black-and-white cards.</p><p>There are still some ideas I have to make it better, the biggest one being asking the user to define the colors for each team so it would be even easier and less error prone to match right the cyclists on the board with the cards on the screen. Another is the aforementioned graphical improvements to make it more appealing to use. <a href="https://play.google.com/store/apps/details?id=com.benoitgourdin.flammerougecompanion&hl=en_US">Benoït Gourdin's mobile application</a> for running Grand Tour tournaments is a good example of matching visual style with the game. I use that in addition to this app to run my Solo Grand Tours.</p><h2 id="conclusion">Conclusion</h2><p>I love being able to build custom things to enhance the board gaming experience. I've always hated crafts since I was a kid but lately with print and play table top games and especially this project, I've gotten excited about them.</p><p>For me, this was a fun project where I could combine my skills in software development with my curiosity in building something tangible. For this project, it only meant gluing pieces of hdf together but hopefully it inspires me to make something of my own in the future as well.</p><p></p>
Adding keyboard shortcuts to Viaplay
2020-08-12T00:00:00Z
https://hamatti.org/posts/adding-keyboard-shortcuts-to-viaplay/
<p>Now that the NHL playoff season has finally arrived, I've found myself watching a lot of hockey through <a href="https://viaplay.fi/">Viaplay</a> streaming service. When watching those games on my laptop, I noticed that I would instinctually reach for <code>m</code> or <code>f</code> key to toggle mute or fullscreen respectively. And I would be disappointed time and time again to notice that it wouldn't work.</p><p>I decided to fix that.</p><p>In May, Brian Lagunas asked on Twitter, <a href="https://twitter.com/brianlagunas/status/1262499751573909504">why people decided to do web development out of all the possible sub-genres of software development</a>. My original answer discussed the distribution but another major reason why I've become so fond of web as a platform is the extensibility. I can write Javascript to modify pretty much anything that's happening on the screen and so I can create the tooling I need for myself even if the original developer didn't provide those.</p><h2 id="the-process-of-exploration">The process of exploration</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/08/viaplay-ui.png" class="kg-image" alt="Screenshot of Viaplay UI, showing the control panel and video feed" /></figure><p>Before starting a new project to build a Chrome Extension, I started from the developer console to see what kind of things I'd be able to do with Javascript to modify the behavior of the app. I figured I could trigger 'click' events to simulate a human clicking on the mute or fullscreen button.</p><pre><code class="language-javascript">document.querySelector(".audio-control").click();</code></pre><p>This worked but only if the user had moved their mouse to reveal the control panel as those items would be removed from the DOM when not being hovered. </p><pre><code class="language-javascript">const showUI = () => {
document
.querySelector(".scene")
.dispatchEvent(new Event("mousemove", { bubbles: true }));
};</code></pre><p>I built a function that triggers a <code>mousemove</code> event and bubbles that up in the DOM to make sure it reaches the correct layer where the control panel will become visible.</p><pre><code class="language-javascript">document.addEventListener("keyup", (event) => {
/* If it's m, we want to mute/unmute */
if (event.key === "m") {
showUI();
document.querySelector(".audio-control").click();
}
})</code></pre><p>With these two pieces, I was able to build the first version of the functionality. Listening to <code>keyup</code> event and in case of <code>'m'</code> being pushed, we show the UI and click on the mute button.</p><h3 id="the-problem-of-clicking">The problem of clicking</h3><p>My code kinda worked but not really. If you have ever used Viaplay, you know that it will cause the video to pause if clicked anywhere outside those buttons. I tried all sorts of things to figure out how it was deciding that functionality but no matter what I tried, it would always pause the video (in addition to correctly applying mute).</p><p>That was bit annoying but I figured that since I was just building it for myself, it was okay for now and started using it. But it wasn't okay, it became very annoying.</p><p>While I was watching the almost record-long playoffs game between Lightning and Blue Jackets last night, a revelation hit me: I could just first pause the game and then quickly hit the action I wanted and maybe it could be made smooth enough.</p><pre><code class="language-javascript">if (event.key === "m") {
document.querySelector(".scene").click();
setTimeout(() => {
showUI();
const muteButton = document.querySelector(".audio-control");
muteButton.click();
}, 20);
}</code></pre><p>So I added a <code>click</code> to pause the video and then run the previous code with a <code>setTimeout</code> of 20ms. For a reason that I don't know yet, fullscreen works fine with a 10ms delay but mute needed some extra time to be available so I put that into 20ms.</p><p>To my surprise, it works brilliantly. I can't even notice the pause.</p><p>I then added fullscreen functionality:</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">if (event.key === "f") {
document.querySelector(".scene").click();
setTimeout(() => {
showUI();
const fullscreen = document.querySelector(".fullscreen");
if (fullscreen) {
fullscreen.click();
} else {
document.querySelector(".no-fullscreen").click();
}
}, 10);
}</code></pre><!--kg-card-end: html--><p>which needed to be a bit more involved because unlike the mute functionality, this one changes classes when toggled.</p><h2 id="the-code">The code</h2><p>Since the project is still in testing and might require some tweaking, I haven't packaged and published it as a Chrome Extension yet. However, all the code is available in GitHub at <a href="https://github.com/Hamatti/viaplay-keyboard-shortcuts">hamatti/viaplay-keyboard-shortcuts</a> and you can install it as an <strong>unpacked extension </strong>through <a href="chrome://extensions/">the extension panel</a> by downloading the code from GitHub and using <strong>Load unpacked </strong>function.</p><p>The code is MIT licensed so it's open source and you can modify and extend it as you wish.</p><p>Thanks to <a href="https://spiceprogram.org/">Futurice's Spice Program</a> for supporting the creation of this software.</p>
Secret Santa PnP – Designer Diary #1: I made some cards! (via BoardGameGeek)
2020-08-09T00:00:00Z
https://hamatti.org/posts/external-designer-diary-1-secret-santa-pnp/
<p>Note: This was originally published as <a href="https://boardgamegeek.com/thread/2479994/article/35534741#35534741">a forum post at BoardGameGeek.com</a> where you can follow the game's development process.</p>
<p>Today I took the first practical steps on the journey to make Secret Santa PnP reality. After crunching some math and finding a decent amount of cards (52 for 4 players, something else for 3 players), I jumped into designing some card templates (I wanted to start from something visual even though I'm no graphic designer).</p>
<p><img src="https://hamatti.org/img/bgg-post-1-1.png" alt="Secret Santa PnP - Human Card" /></p>
<p>After I had some idea of what my humans and gifts will look like, I jumped on my trusty notebook and started drafting ideas for gifts. I knew I wanted some categories (like monetary value, $ – $$$ and thematic categories like food) but I wasn't sure how to make a good balance. I tried a few methods (started by assigning a $ value to 24 items, each $ got 8) but finally got a nice mixture by building an Excel sheet, manually assigning base and randomizing the rest and then balancing out by hand.</p>
<p>So now I had 8 categories and 24 items so I got onto my game building workshop (blank A4s, sleeves and a paper cutter) and drew all the 52 cards: 32 humans/employees/Christmas enthusiasts (terminology pending) and 20 unique gifts (actually I did 24 since I messed up math but will downsize to 20) as seen below.</p>
<p><img src="https://hamatti.org/img/bgg-post-1-2.jpg" alt="Secret Santa PnP first prototype cards" /></p>
<p>Each gift has a name, awesome drawing of them and 1-3 categories (out of 8 possible) that they belong to.</p>
<p>Each person has a name, face, Gift-Giver Personality and Gift-Receiver Personality which defines which gifts score points and which don't. Basically you want to match both personalities with the categories of the gift to maximize your score.</p>
<p><img src="https://hamatti.org/img/bgg-post-1-3.jpg" alt="Example of Secret Santa PnP final formation for a single player" /></p>
<p>Above is an example of the final grid. In four player game, you draft 13 cards (4 x 3 + 1) and you're required to place your card either immediately to the play area (grid) or to a reserve spot to be used later.</p>
<p>After a few fully randomized (just drawing right amount of cards from the deck) playthrough, I got maximum score which was expected since I decided to start with very broad categories. Most gifts now have 3 categories which will be toned down after more playtesting.</p>
<p>I also started making the first rule cards (trying to fit all rules to 2 standard sized cards) and learned that it's super hard. I have played hundreds of board games and thought I'd know how to write rules having read so many of them but turns out it's harder than I expected.</p>
<p>Currently the scoring is based on successful connections (don't know what to call them yet). If a giver buys a gift that matches their personality, that's +1VP. If the gift matches the receivers personality, that's +1VP. A complete row is +1VP. If the grid is not complete (drafted too many/few of some type), that's -2VP. So maximum is 12VP. All of that is subject to change once I find out how well players end up scoring.</p>
<p>Next steps are to test, test, test and test and also start thinking about the visuals. I'll probably go with icons for categories rather than words to make it bit nicer looking but let's see where the road takes me.</p>
<p>All feedback is welcomed!</p>
Recreational Mathematics
2020-07-22T00:00:00Z
https://hamatti.org/posts/recreational-mathematics/
<p>As I'm returning from a very relaxing 5-week holiday, I figured I'd get back into my weekly blogging with a rather casual topic. I'll be writing about some technical stuff like naming, learning and scraping in the upcoming weeks but today we'll talk about a guilty pleasure of mine: <strong>recreational mathematics.</strong></p><p>As a younger man, I was quite much into math. In the middle school and high school, I was actually guide good at it (I even participated in a national math competition type of thing in the 7th grade). Then as I entered work life, I didn't do much math and by the time I got into university's math courses, I was completely out of touch and had a really hard time.</p><p>And even though I still haven't picked up learning real math (like derivatives and integrals or linear algebra), I do enjoy the fascinating world of mathematics from a rather different perspective of recreational math. Wikipedia defines it as:</p><blockquote><strong>Recreational mathematics</strong> is mathematics carried out for recreation (entertainment) rather than as a strictly research and application-based professional activity or as a subject that one is learning such as, for example, at school.</blockquote><p>For me, it mostly comes in form of watching stuff on the Internet - quite often in Youtube. I love things that are fascinating in mathematics but either presented or applied in a obscure or fun way.</p><h2 id="prime-numbers">Prime numbers</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/07/prime-spirals.png" class="kg-image" alt="Spirals generated by prime numbers" /></figure><p>One of my favorite sub-topics is prime numbers. Youtuber 3Blue1Brown has a collection of amazing math videos but one that always impresses me is <a href="https://www.youtube.com/watch?v=EK32jo7i5LQ">his video on the prime numbers</a>. In the video, he not only talks about interesting properties of prime numbers, he combines it with amazing visuals that are a cornerstone of his videos.</p><p>Another favorite from the world of prime numbers is <a href="https://www.youtube.com/watch?v=LvFvtjn3wgg">Dr. Holly Krieger's lecture <em>Experimenting with Primes</em></a><em>. </em>Due to its nature, it's bit more educational but I love how she approaches primes in a very intuitive way and once she gets into dynamical sequences, the math gets so beautiful.</p><h2 id="general-math">General math</h2><p>Next one is a book but luckily she's also done a lot of videos and public performances. <a href="https://www.amazon.co.uk/gp/product/1784162744/">Hannah Fry's The Indisputable Existence of Santa Claus</a> is a must read for anyone interested in recreational mathematics. If you <a href="https://www.youtube.com/results?search_query=hannah+fry">search Youtube with her name</a>, you'll find a huge collection of interesting things she's done. She's amazing at bringing mathematical concepts to the level that anyone can understand. I can highly recommend her lectures in The Royal Institution, her TED talk about mathematics of love.</p><h2 id="higher-dimensions">Higher Dimensions</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/07/higher-dimensions.png" class="kg-image" alt="A computer rendering of a 4D object passing through 3D world." /></figure><p> The Royal Institution is a nice segway to my next topic which is dimensions. Actually understanding anything beyond third dimension is very difficult and brain melting but it can create some amazing recreational math. <a href="https://www.youtube.com/watch?v=1wAaI_6b9JE">Matt Parker visited The Royal Institute to talk about Four Dimensional Maths</a> in a very delightful presentation (Matt is all around great performer). You gotta check out the 3D Shadow hat at the end of the talk!</p><p>Since circles are very math-y, let's circle back a bit. I started my examples with 3Blue1Brown and as we reached the section of higher dimensions, his guest episode in <a href="https://www.youtube.com/channel/UCoxcjq-8xIDTYp3uz647V5A">Numberphile</a> (another really great recreational math Youtube channel) was wonderful. <a href="https://www.youtube.com/watch?v=6_yU9eJ0NxA">In this episode</a>, he presents a darts game that is played in higher dimensions and teaches about calculating volumes of nD balls, probabilities and all that beautiful stuff.</p><h2 id="recreational-physics">Recreational Physics</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/07/what-if-example.jpg" class="kg-image" alt="A comic strip of baseball pitcher throwing a ball at 0.8 times the speed of light" /></figure><p>I'm not much of a physics guy but since we're talking about recreational sciences, I had to add a mention of <a href="https://www.amazon.com/What-If-Scientific-Hypothetical-Questions-ebook/dp/B00IYUYF4A">Randall Munroe's book What If?: Serious Scientific Answers to Absurd Hypothetical Questions</a>. Munroe is a physicist who's mostly known for his wonderful <a href="https://xkcd.com/">xkcd</a> comics. He wrote a book about hypothetical situations that are hilarious, fascinating and tangentially relevant to real life.</p><p>If you're not much of a book person (this one has a lot of pictures though!), he also did <a href="https://www.youtube.com/watch?v=I64CQp6z0Pk">a TED talk about his book</a>. </p>
Hidden Identity in Table Top Games
2020-06-17T00:00:00Z
https://hamatti.org/posts/hidden-identity-in-table-top-games/
<p>One of my favorite table top gaming genres is hidden identity games. This is mainly because I like the idea of co-op games (where all players are on the same side) but hate the actual way those end up being played. In a co-op table top game, unless the rules dictate you cannot talk or mime, the game can easily devolve into a situation where the most experienced player can tell everyone what the smartest move is and games became fight against randomness/luck.</p><p>However, with hidden identity, these games become masterpieces. I like to think I'm a nice guy in real life but when I play games, I'm willing to do pretty much anything within the context of the game to win. I have (figuratively) backstabbed so many good friends so badly that I accidentally once even hurt that relationship outside the game (that was not a good thing and it made me sad).</p><p>Let's start with some definitions and I'll later discuss some of the games in the genre I really like and some that I don't like. <strong>Hidden Identity</strong> refers to games where players generally play together towards a shared goal but one or more players secretly know that their mission is to make the team fail (or they have a way to actively win). The key here is, that in a good hidden identity game, the "good" side has a way to win the game if they find out who are on the other side.</p><p>This mechanic immediately solves the problem I have with co-op games: you cannot trust the most experienced player so everyone has to make their own decisions. I think they also teach skills like argumentation, critical analysis of arguments and decision making with incomplete information. And the social aspect make them so much fun, especially with good games and large enough groups.</p><p>Let's talk about some of them! First, we'll take a look at three games that are mostly based on social bluffing, then two games with ever-changing hidden identities and last two games where gameplay actions are in the focus with some hidden identity obfuscating the decision making. Finally, we're gonna look at a collection of other games fitting the genre.</p><h2 id="werewolf-mafia-lupus-in-tabula">Werewolf / Mafia / Lupus in Tabula</h2><p><em>Couldn't find any interesting video of the gameplay. Go experience it yourself.</em></p><p>My absolute favorite one has many names. Some call it Werewolf, some play it with a Mafia theme and at least one of the commercial versions runs by the name of <a href="https://boardgamegeek.com/boardgame/63539/lupus-tabula">Lupus in Tabula</a>. This is a great game because you can play it with almost any size of a group you want and it adapts well. The BoardGameGeek site recommends 8 to 24 players, based on my experience of probably 100+ session, around 14 to 18 is my favorite. Another great thing is that you don't actually need to buy anything. You can make the cards from pieces of paper and other than your hidden identity, the game is based 100% on discussion.</p><p>Werewolf is a social bluffing game at its best. With large enough group, you have to remember dozens of rounds of arguments and lies and build connections between what people have said earlier and apply some very light game theory math to figure out the werewolves. And because the game is pretty much all about discussion, there are unlimited amount of strategies.</p><p>Nothing is more satisfying than telling about one of these strategies to the group casually before the games, then apply that strategy boldly during the game play and win. The higher risks you take, the bigger the personal satisfaction is if you succeed.</p><h3 id="pros">Pros</h3><ul><li>Very simple gameplay </li><li>Heavy emphasis on your ability to lie and detect lies</li><li>Who can you trust?</li></ul><h3 id="cons">Cons</h3><ul><li>Needs a big playing group so it's very hard to play often (I miss my university gaming crew)</li><li>If you're removed early, there's a lot of down time and not everyone enjoys following the game</li></ul><h2 id="secret-hitler">Secret Hitler</h2><p><em><a href="https://www.youtube.com/watch?v=Q9Wb0sc5Ft8">Watch Polygon people play Secret Hitler</a></em></p><p>To continue within the same realm of games, <a href="https://boardgamegeek.com/boardgame/188834/secret-hitler">Secret Hitler</a> is a controversially named and themed game where the majority of game play is still in discussions between people but it adds quite a lot of game play elements compared to Werewolf.</p><p>In addition to secret party affiliation and identities, there's voting, special actions taken by elected leaders at certain points of games and element of randomness. I think it's bit easier for newer players than Werewolf because you can always rely your lies a bit more on these elements and you don't have to come up with all the lies from thin air.</p><p>It's still a great game and can be played with a smaller group than Werewolf, which is why it's mostly taken the place in my gaming circles as the go-to social bluffing game. It can be played with 5 to 10 players, requires special cards, envelopes and game board but it was originally made as <a href="https://boardgamegeek.com/filepage/125295/pnp-file-autor">a print-and-play game</a> so you are allowed to print your own pieces. There are also PnP re-themes in Star Wars and Harry Potter lore if you want to avoid the theme.</p><h3 id="pros-1">Pros</h3><ul><li>Can be played with a smaller group</li><li>Easier for beginners to get involved with the genre</li><li>Mostly about your ability to bluff</li></ul><h3 id="cons-1">Cons</h3><ul><li>I don't like random elements in my bluffing games</li><li>The theme is controversial and you might want to check with your group if people are ok with it</li></ul><h2 id="the-resistance">The Resistance</h2><p><em><a href="https://www.youtube.com/watch?v=g_QRczGzXqw">Watch Wil Wheaton and his friends play The Resistance</a></em></p><p>This social bluffing game is somewhere between the two previous ones. In <a href="https://boardgamegeek.com/boardgame/41114/resistance">Resistance</a>, you belong to one of two teams and try to help your team win without giving away if you're in the "bad team". Gameplay is based on voting for members in scifi missions and then selected players decide to either succeed or fail the mission. If good team wins enough rounds, they win the game.</p><p>I used to like Resistance a lot because it was easy and fast to setup even with a small group of players. We often played it as a warm up while waiting for enough players to arrive for a full night of Werewolf. But the more I played it and the more I played other games in this list, the less I liked about it. I think the limitations in how the rounds and voting is structured limits the amount of good strategies. </p><p>Many times, regardless of your affiliation, you have to play the first few rounds as if you're on the good team but then the runway just runs out too fast and the final outcome of the game relies bit too much on who's turn it is to select the mission team.</p><p>In a 5-player game, there are 5 rounds and good team has to win 3/5. On the contrary, Secret Hitler for 5 players can go up to 10 rounds and since there's multiple levels of decision making (voting, president's selection & chancellor's selection) combined with randomness in SH, there are so many more strategies you can play.</p><h3 id="pros-2">Pros</h3><ul><li>Nice for small groups</li><li>Since voting is public, you must learn to justify your decisions</li></ul><h3 id="cons-2">Cons</h3><ul><li>Too limited amount of effective strategies</li><li>Too short to build good cons</li></ul><h2 id="coup">Coup</h2><p><em><a href="https://www.youtube.com/watch?v=BrjCPnbEMfE">Watch Polygon people play Coup</a></em></p><p>If discussion-based bluffing is not your thing, another hidden identity sub genre are games with changing identities with abilities that allow you to take actions. First of them is <a href="https://boardgamegeek.com/boardgame/131357/coup">Coup</a> in which you have multiple hidden identities and you take turns performing actions enabled by the cards.</p><p>What makes it hidden identity & bluffing game is that you can choose to pose as any character and use their abilities as long as you can convince others that you are not bluffing. A major difference to three games above is that in Coup (and later in Love Letter), everyone plays for themselves and there's no co-op element in the game (technically, there is in the expansion but that's also not set to stone).</p><p>While Werewolf, Secret Hitler and Resistance are mostly about trying to find out who's your ally and convincing others that you're on the good side, Coup is all about bluffing about your actions. I feel like it adds a level of guessing, which is not great in my opinion in this genre of games because you can constantly change who you claim to be and calling someone's bluff is often based on "they have been getting away too far, someone has to try to stop them" and occasionally on "I have two out of three of those cards so it's statistically unlikely that they are who they claim". This means it's much less about building convincing and long-term cons, which is not necessarily a bad thing but makes a big difference to previous ones.</p><p>Coup is a really nice small game that is nice to play if you only have a few people (2-6 players), for example when waiting for others to join or between longer games. The extension brings some new characters (which to be honest don't feel like making too much of a difference) and a concept of factions (which improves the game a lot but takes away from the bluffing side).</p><h3 id="pros-3">Pros</h3><ul><li>Easy to learn, easy to play</li><li>Fast games</li><li>Factions in expansion are a nice addition</li></ul><h3 id="cons-3">Cons</h3><ul><li>Bit too much guessing and not about revealing lies</li></ul><h2 id="love-letter">Love Letter</h2><p><em><a href="https://www.youtube.com/watch?v=k2YUYPDq7gQ">Watch Wil Wheaton and his friends play Love Letter</a></em></p><p>In the same sub genre of games than Coup is <a href="https://boardgamegeek.com/boardgame/129622/love-letter">Love Letter</a>. It's a small game for 2-4 players (best with 4, I have never played with 2) with a small handful of cards and fast-paced action. Similar to Coup, your secret role changes every turn and your aim is to remove other players from the game by mostly guessing which card they are holding at the moment (it's bit more complex and fun than it sounds).</p><p>A single round only takes a few minutes and it's more on the side of deduction than bluffing, especially with your identity changing (or at least, potentially changing) every turn. I love it especially because it's a great gateway game for people who don't think they like board games. There's basically zero setup (shuffle deck) and you can explain the rules in a few minutes and after a round or two, everyone knows the rules.</p><h3 id="pros-4">Pros</h3><ul><li>Great gateway game for new players</li><li>Fast-paced gameplay</li><li>Lightweight bluffing</li></ul><h3 id="cons-4">Cons</h3><ul><li>I don't know really. It's a great game</li><li>In this list, maybe not so much about secret identities.</li></ul><h2 id="battlestar-galactica">Battlestar Galactica</h2><p><em><a href="https://www.youtube.com/watch?v=Q-jQz15dUVU">Watch The Cynical Brit and his friends play BSG</a></em></p><p>From mostly bluffing to ever-changing identities to big box games with a hint of hidden identity: <a href="https://boardgamegeek.com/boardgame/37111/battlestar-galactica-board-game">Battlestar Galactica</a> (and its expansions). I absolutely love this game. In BSG (based on a TV series of the same name), your job is to escape from evil Cylon's who are chasing you. The game has multiple characters with abilities and actions (these are public) and two hidden factions (humans and Cylons) and most of the game is a more traditional table top gaming with taking actions, playing cards and trying to beat the game itself.</p><p>The twist comes from the fact that Cylons are trying to sabotage humans' mission but they cannot let humans know too early. So at the same time, your focus is often on playing against the game but you always also have to remember what others are doing and since it's a fairly complex game with many options, it's never obvious if a single action is malicious or just sub-optimal.</p><p>A single game lasts from 2 to 3 hours and with some expansions (my favorite is the <a href="https://boardgamegeek.com/boardgameexpansion/43539/battlestar-galactica-board-game-pegasus-expansion">Pegasus expansion</a>), there are so much well designed game play action that the time flies. Players absolutely must play together from the very beginning and there's not much room for doubt and accusations while at the same time, you absolutely have to keep them in mind all the time.</p><p>While the social bluffing games at the beginning of the list are mostly just about finding out who's in the bad team, in BSG, that's just not enough. You still have to beat a very difficult game that is constantly throwing problems at your face. And the Cylon players have an option to reveal themselves which gives them extra tools to make human players' mission even harder.</p><p>It's a wonderful game and if you have been playing complex board games before, I highly recommend checking it out.</p><h3 id="pros-5">Pros</h3><ul><li>It's amazing</li><li>It's a full-on board game</li><li>Actions are very rarely clearly malicious so it's hard to figure out who's who</li></ul><h3 id="cons-5">Cons</h3><ul><li>It makes me want to watch BSG all over again and there's only so little time</li></ul><h2 id="shadows-over-camelot">Shadows over Camelot</h2><p><em><a href="https://www.youtube.com/watch?v=Ii3yhl8wra0">Watch Wil Wheaton and his friends play Shadows over Camelot</a></em></p><p>Second game in a similar genre than BSG (big box game, co-op with a traitor) is <a href="https://boardgamegeek.com/boardgame/15062/shadows-over-camelot">Shadows over Camelot</a> which pits the players to fight as the knights of the round table to protect Camelot.</p><p>I have to admit, I don't like the game so much. I think the way its parts are organized, makes it way too easy for the traitor to mess up good team's attempts. Even without a traitor, the game is quite difficult to beat and I think it's too easy to do malicious actions without being caught that it's not fun enough for the traitor role.</p><p>In this game, players mostly play alone towards a common goal which decreases the amount of interaction between players which I think is crucial in this genre to make it fun. </p><h3 id="pros-6">Pros</h3><ul><li>It's a quite nice game for beginners of the genre</li><li>King Arthur is such a great lore</li><li>It's cool to be a Knight of the Round Table</li></ul><h3 id="cons-6">Cons</h3><ul><li>Not enough interaction between players</li><li>Too easy to be the evil player</li></ul><h2 id="one-night-ultimate-werewolf">One Night Ultimate Werewolf </h2><p><em><a href="https://www.youtube.com/watch?v=bJ4Hrp8gQ-E">Watch Polygon people play One Night Ultimate Werewolf</a></em></p><p>Werewolf became such a popular game that it has inspired multiple variants. One of the most popular ones is <a href="https://boardgamegeek.com/boardgame/147949/one-night-ultimate-werewolf">One Night Ultimate Werewolf</a> which only requires 3 to 10 players and lasts one round. I wanted to add it to this list, not because I think it's a great game (many like, I think it's rather bad) but because it allows me to discuss the mechanics of these games that I really like.</p><p>There are two major elements that make me a big fan of these games: <strong>deductive reasoning </strong>and <strong>long-term bluffs. </strong>This game kinda lacks both and its biggest design flaw is how limited the information is. Catching someone from lying is only fun if they had a proper opportunity to lie. And same goes for convincing your fellow players that you're on their side or bluffing them. </p><p>One Night Ultimate Werewolf doesn't give players nearly enough information to make it more than a mere guessing game with slightly elevated odds. Werewolves still know who they are and who's on their team but most of the other actions are done in isolation and there's no way to confirm any actions on a following night.</p><p>Let's look at an example: in original Werewolf game (with enough players), you have two characters on human side that have a great synergy. <strong>Seer</strong> can pick a player during a night phase and know if they are a werewolf or not. <strong>Medium </strong>gets to see if a villager who was voted to kill was a werewolf or not. This enables a flow where Seer can accuse someone from being a werewolf, players vote to kill them and then Medium can confirm it on the following round.</p><p>What makes it even better is that when you have a dozen rounds, there are so many decisions to make with these roles. When is it safe to let werewolves know you are one of the most powerful human roles? If you're bluffing, can you remember your lies throughout the game and be consistent? What happens if multiple players claim to be the Seer? </p><p>One Night Ultimate Werewolf doesn't allow any of that. The Seer just sees one player's identity and that's it. They can try to convince others they are who they say but nobody really has a way to verify it.</p><p>And because of the other roles, namely <strong>Robber </strong>and <strong>Troublemaker</strong> unique to this version, even players themselves don't know at the end who they are which makes all lying and convincing eventually meaningless. You may start as a Seer and become Werewolf without you knowing so there's a very high probability you end up playing the wrong role and winning isn't in your hands anymore.</p><p>It's not about convincing others about some secrets that you know because you don't know them anymore not even about yourself. And that reduces the game into an unsatisfying guessing game. Where's the fun of trying to convince others of something if at the end, it's kinda a coin flip if you're even right.</p><p>I understand the appeal that this game has and a lot of people seem to like it for some reason unknown to me. So if you're into this kind of genre and are looking for something less serious, maybe check it out. I just think its design and mechanics work against all the good that this game and genre have to offer.</p><h2 id="push-the-button">Push the Button</h2><p><em><a href="https://www.youtube.com/watch?v=Y5VSq3zF4Ws">Watch Polygon people play Push the Button</a></em></p><p>Finally, a game I ran into very lately but which seems really fun. I haven't played it enough to yet know where it falls into the scale of "I'm in control of lies and if people believe me, I win" and "The game mechanics drive the winning condition too much". On my limited experience so far, it's quite nicely on the first end.</p><p><a href="https://www.jackboxgames.com/party-pack-six/">Push the Button</a> is part of Jackbox Party Games which already is a sign of quality gaming. It's included in the Party Pack 6.</p><p>In the game (for 4 to 10 players), some players are aliens and others humans in a spaceship with a countdown timer. You have a short time (< 20 mins) to figure out who's human and who's an alien and at the end, unanimously vote correctly to win. To find out who's who, the Captain (a role that moves from one player to another every turn) can choose a test from a few possibilities and select 2 to 3 players to participate in the test.</p><p>Players are given different kind of prompts to which they have to answer: in one game you have to make a drawing with your mobile phone, in another you give your opinion on a likert scale, in a third you have to provide an answer to a question and so on.</p><p>The twist is that the alien receives a slightly altered prompt. Something that might end up with a very similar answer than the humans do but most likely not quite. Using a series of these tests and picking your test subjects smartly you try to deduce who's alien and who's not. The aliens have an ace in the sleeve too: they have a limited amount of <strong>hacks </strong>which allow them to change those prompts. They can send a human player the alien prompt and vice versa to confuse players.</p><p>The time limit and the creativeness of the tests make it a really fun game. It's a well built digital game (like all the Jackbox Party Games are) and so much fun in parties and works nicely even via a video call on times when you can't gather together with your gaming crew. There's enough information to make educated guesses and enough twists to balance the opportunities for both sides.</p><h3 id="pros-7">Pros</h3><ul><li>Jackbox Party Games are so well made</li><li>Creativity</li><li>Nice balance with twists and unanimous voting</li></ul><h3 id="cons-7">Cons</h3><ul><li>I wish I had more friends to play this with</li></ul><h2 id="betrayal-at-the-house-on-the-hill">Betrayal at the House on the Hill</h2><p><em><a href="https://www.youtube.com/watch?v=IrKiPrZN__I">Watch Polygon people play Betrayal at the House on the Hill</a></em></p><p>This game is a unique twist to the genre. It starts as any co-op game where players explore a house on the hill, collect items and try to stay sane. What makes it interesting, is that the traitor role is revealed in the middle of the game and then the game turns into a match between good and evil. This forces players to play together but with caution because nobody knows who will end up being a traitor.</p><p>It completely lacks the social bluffing aspect because the identities are hidden from everyone and revealed at once to everyone. But since it is technically a hidden identity game, I wanted to add it to the list.</p><p>This mechanic solves the main issue I talked about at the beginning of this post: you can't simply follow a single player's advice because everyone should be making a bit selfish decisions (or at least decisions that are never negative to them while benefiting the group) rather than helping the group to win.</p><h3 id="pros-8">Pros</h3><ul><li>You have to balance individual and group improvement when making decisions</li></ul><h3 id="cons-8">Cons</h3><ul><li>Lacks social bluffing</li><li>Not enough difference between runs even though there are a lot of different haunts</li></ul><h2 id="conclusion">Conclusion</h2><p>Hidden identity games are one of the most fun table top game genres. They can be bit intimidating to begin with and if you go overboard with your cons, they can really hurt relationships. So remember, gaming is just gaming.</p><p>One downside with all these games is that they can be challenging for beginners of the genre because sometimes not talking enough can be seen as suspicious and people can lose their interest in the genre because they are always outed in the beginning due to not knowing what to say and how much. So be nice to new players when introducing them to these games and sometimes give them a benefit of doubt when they are not playing perfectly – but not too long because staying under the radar is a wonderful strategy to win games.</p>
I built a digital version of Black Hole game
2020-06-10T00:00:00Z
https://hamatti.org/posts/i-built-a-digital-version-of-black-hole-game/
<p><em><a href="https://the-blackhole-game.netlify.app/">Try the Black Hole game yourself.</a> </em></p><p>If you have been reading my blog for a while or know me from elsewhere, you might have noticed that I absolutely love board games. Digital games have their benefits but nothing beats sitting down with friends, grabbing a couple of beers and playing games with physical cards, tokens and boards. Or in the case of my <a href="https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/">Minimal Travel Table Top Game Collection 2: Social Distancing Edition</a>, playing alone with physical cards and tokens.</p><p>Board games are also great for practicing my software development skills. Every now and then, I pick a game I'm familiar with from the physical world and try to build a web version of it with the digital skills I have. Sometimes I even play those games with friends but most of the time, I just build them because I like writing code that implements board games.</p><p>I like implementing board games in digital format because they are relatively simple in comparison with digital games in general: everything is basically in 2D and almost any action can be reduced to <em>click </em>or <em>drag-and-drop </em>which means my web dev skills come in handy. They also have very well defined interactions between different items and the rules are written in a logical way which makes the transformation process from physical to digital interesting.</p><h2 id="black-hole">Black Hole</h2><p>The other night, I was watching one of my favorite Youtube channels: <a href="https://www.youtube.com/channel/UCBa659QWEk1AI4Tg--mrJ2A">Tom Scott</a> and rewatching his <a href="https://www.youtube.com/watch?v=4gZwGTT3jXg&list=PL96C35uN7xGIEA87qPL0DVYqfLm25WHC_">Game On series</a>. One of the games they play in that show is <a href="https://nestorgames.com/#blackhole_detail">Black Hole, designed by Wal Joris</a>. I really like that game because it's fast to learn and play but still provides interesting choices and strategies when playing with a friend.</p><p>So that night, I couldn't sleep so I decided to give it a go and see if I could implement the game in web world. Eventually, I want to make the game playable over the Internet but the first version is implementation of the mechanics and rules on a shared computer experience.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/gameplay-loop.gif" class="kg-image" alt="Animation of game play with blue and red player placing tokens on the board after each other" /></figure><p>Here are the rules of the game:</p><ul><li>There are 21 slots shaped in a form of a pyramid</li><li>Two players take turns and place numbered tokens starting from 1 and ending with 10 on empty slots in the board</li><li>At the end, one slot will be left empty and it becomes the black hole</li><li>Points are scored as a sum of tokens that are adjacent to the black hole</li><li>Player with least points wins the round</li></ul><p>There are also some rule variants for the game where instead of placing the numbers from 1 to 10, the order of tokens is randomized. My version only deals with the base rules (at least for now).</p><h2 id="tech-design">Tech & Design</h2><p>I had a pretty clear vision of how to implement this. I chose to use React since that's what I've used the most in the recent months and would have one less thing to worry. I decided to make each slot a component <code><Slot /></code> that would keep track of its own state (mainly color and number after a token has been placed).</p><p>All the following code examples are small pieces of the codebase and I have redacted some pieces of code for clarity. You can find <a href="https://github.com/Hamatti/blackhole-game">all the code (which is MIT licensed) in my GitHub repository.</a></p><pre><code class="language-javascript">const Slot = ({ slot, onClick, gameStatus }) => {
if (
gameStatus === GAMESTATE.FINISHED ||
slot.state !== SLOTSTATE.UNSELECTED
) {
onClick = () => {};
}
const { idx, state, number, color, highlight } = slot;
const [left, top] = calculatePosition(idx);
// - - redacted styles definitions for brevity - -
return (
<button
disabled={
state !== SLOTSTATE.UNSELECTED || gameStatus === GAMESTATE.FINISHED
}
className="slot"
style={style}
onClick={() => onClick(slot)}
>
{state === SLOTSTATE.UNSELECTED ? "" : number}
</button>
);
};</code></pre><p>Here's what the <code><Slot /></code> looks like. I'm not 100% sure if I need the overwriting of <code>onClick</code> on lines 2–7 as I do disable the button when selected or game ends but I put it there as a extra precaution.</p><p>Each slot is a HTML button that gets positioned absolute and <code>calculatePosition</code> function returns the correct position in regard of the button's index in the pyramid. Once Slot button is clicked, it will change its background color (style definitions redacted for clarity) according to the player and show the number of the token placed.</p><p>At the beginning of the game, each slot is generated with some hardcoded rules:</p><pre><code class="language-javascript">const createSlots = () => {
let slots = [];
let col = 0;
let row = 0;
for (let i = 0; i < 21; i++) {
if (i === 6 || i === 11 || i === 15 || i === 18 || i === 20) {
row += 1;
col = 0;
}
const idx = [row, col];
col += 1;
slots.push({
i,
idx,
state: SLOTSTATE.UNSELECTED,
number: null,
color: null,
highlight: false,
});
}
return slots;
};</code></pre><p>On line 6, the breakpoints are when new row of the pyramid is started. Indexes for position calculation are stored as <code>[row, col]</code> and a slot object contains information about the number, color and current state. <code>highlight</code> is used when scoring to display adjacent slots to the black hole.</p><p>My biggest hurdle was figuring out the adjacency of two different slots. I even considered hardcoding those relationships into slots themselves but was finally able to make it universal:</p><pre><code class="language-javascript">const isAdjacent = (slotA, slotB) => {
return (
(isSameRow(slotA, slotB) && isNextToEachOtherOnSameRow(slotA, slotB)) ||
(isAdjacentRow(slotA, slotB) && isSameColumn(slotA, slotB)) ||
(isPreviousRow(slotA, slotB) && isNextColumn(slotA, slotB)) ||
(isNextRow(slotA, slotB) && isPreviousColumn(slotA, slotB))
);
};</code></pre><p>There are four ways two slots can be adjacent:</p><ol><li>They are on the same row in columns next to each other (for example, <code>[0,0]</code> and <code>[0,1]</code></li><li>They are on adjacent rows in the same column (for example <code>[0,0]</code> and <code>[1,0]</code>)</li><li>Black hole is on a row above the other one and in column + 1 (for example, <code>[1,2]</code> and <code>[0,3]</code></li><li>Black hole is on a row below the other one and in column - 1 (for example, <code>[1,2]</code> and <code>[2,1]</code></li></ol><p>Even my inability to explain it above in a clear way is a good indicator of how much trouble I had with it.</p><p>Since the problem space is finite and small (21 slots and each slot compared against 20 other), I decided to hardcode all these comparisons into a test suite. For each slot index, I created a full test pattern against each other slot. I marked down all the ones I know should be adjacent and made the failing set a difference from the success set.</p><pre><code class="language-javascript">describe("[0,0]", () => {
it("should match correctly", () => {
const empty = { idx: [0, 0] };
const matches = [{ idx: [0, 1] }, { idx: [1, 0] }];
const fails = all.filter((slot) => {
const slotIdx = JSON.stringify(slot.idx);
for (let i = 0; i < matches.length; i++) {
if (slotIdx === JSON.stringify(matches[i].idx)) {
return false;
}
}
return true;
});
expect(fails.length).toEqual(21 - matches.length);
matches.forEach((match) => {
expect(isAdjacent(empty, match)).toEqual(true);
});
fails.forEach((fail) => {
expect(isAdjacent(empty, fail)).toEqual(false);
});
});
});</code></pre><p>In the above, <code>all</code> is an array of all slots. Now I have 21 test cases where each one of those 21 slots is compared against every other slot to make sure my comparison function does what I want it to do. Instead of trying to come up with corner cases to a challenge I already a bit struggled with, I went with full coverage to make sure my corner case selection wasn't flawed.</p><p>One last piece of code I want to show is how I handle the clicks and advancement of the gameplay.</p><pre><code class="language-javascript">const clickHandler = (slot) => {
const newSlot = gameState.slots[slot.i];
newSlot.number = gameState.currentValue;
newSlot.color = gameState.currentPlayer;
newSlot.state = SLOTSTATE.SELECTED;
const newValue =
gameState.currentPlayer === PLAYER.RED
? gameState.currentValue
: gameState.currentValue + 1;
const newPlayer =
gameState.currentPlayer === PLAYER.RED ? PLAYER.BLUE : PLAYER.RED;
let status = gameState.status;
if (newValue === 11) {
status = GAMESTATE.FINISHED;
}
setGameState({
currentPlayer: newPlayer,
currentValue: newValue,
slots: gameState.slots,
status,
});
};</code></pre><p>Lines 2–5 are probable causes for some trouble down the line because they modify the state directly. I need to clean that up.</p><p>How it works is that there are three main elements in addition to the slots: <code>currentPlayer</code>, <code>currentValue</code> and <code>status</code>. </p><p><code>currentPlayer</code> toggles between <code>RED</code> and <code>BLUE</code> and whenever it's been <code>BLUE</code>'s turn, we also increment the <code>currentValue</code> to signify that the game has moved to the next token. And if the new value would reach 11, we set the <code>status</code> to <code>FINISHED</code> which triggers the calculation of points and turns off the ability to place tokens on the game board. Finally, a scoreboard is shown and the game ends. You can play another round by refreshing the page.</p><h2 id="what-s-next">What's next?</h2><p>The first version is very limited by design. I wanted to make something that works and push that to the world so at least I've been able to finish it if I don't find time or interest in the future for this particular game.</p><p>However, there are a couple of things I'd like to add:</p><ol><li>Online multiplayer: I want to make it possible to play with friends over the Internet and not just on the same computer.</li><li>Second game mode: I mentioned earlier the randomized tokens version of this game.</li><li>Change colors: Picking your own colors would be nice if red and blue are not good options for the player.</li><li>Display of all 20 tokens on the side (as you would see the physical tokens on the board as well).</li><li>Maybe some animations for placing a token.</li></ol><p>My motivation to build it was to practice my basic programming skills and see what this game looks like when transforming the physical game into digital realm. For that, I reached my goal and I had a lot of fun. All the things above are small things I might work on when I feel like it.</p><p>And if you think implementing one of them would be a fun exercise, <a href="https://github.com/Hamatti/blackhole-game">the code is on GitHub</a> with open source license and I'm happy to welcome pull requests.</p>
Different approaches to learning programming
2020-06-03T00:00:00Z
https://hamatti.org/posts/different-approaches-to-learning-programming/
<p><em>The opinions presented in this post are based on my personal experience as both a learner and a teacher. It is not based on academic research. If you're interested in learning more about teaching from academic perspective, I recommend taking a look at the publications of <a href="https://www.utu.fi/en/people/mikko-jussi-laakso">Mikko-Jussi Laakso from the University of Turku</a> or <a href="https://research.it.abo.fi/people/lgrandel">Linda Mannila from Åbo Akademi</a> just to name a few.</em></p><p>I was reading <a href="https://twitter.com/anttiviljami">Viljami Kuosmanen</a>'s <em><a href="https://anttiviljami.github.io/blog/The-1-thing-I-wish-someone-told-me-before-I-started-programming">The #1 thing I wish someone told me before I started programming</a> </em>and it resonated very much with me. I've tried many approaches in learning programming myself and even more lately, I've helped a lot of people get their feet wet with programming.</p><p>In this post, I'll compare two approaches that are on the opposing sides of a single viewpoint: the ground up approach starting from theory and gaining good basic understanding versus the hands-on approach of creating software and learning as you go. To anyone looking for a quick answer on which is better: <strong>they are both good approaches.</strong></p><h2 id="the-hands-on-approach">The hands-on approach</h2><p>I have organized and coached in a lot of workshops that take a very hands-on approach. Workshops like Rails Girls, Django Girls, Mimmit Koodaa and ReacTour have good guides that students can follow to build a modern web application. These workshops, running from 1 to 2 days, don't do deep dives into a single topic but give an overview to what goes into building an application: creating a new project, building models/database schema, backend and frontend code and deployment to a cloud service.</p><p>At the end of those workshops, you can share a link to your app to a friend and be proud in saying "I built this."</p><p>It's clear that you won't become a proficient developer in a two-day workshop copy-pasting code from a guide but that's not the point of these workshops. The point is to provide feeling of accomplishment, a peer group of people in the same situation and guidance to get started.</p><p>Once you have built this first application, building the second one – your own – become a magnitude easier. Instead of starting from nothing, you have something you can reuse, a guide you can fall back to and little by little start changing things. I'm sure many of us have had the experience of how it's so much easier to make progress when you have something in existence rather than starting from the scratch.</p><p>Starting with an approach like this doesn't mean you never learn the basics. Knowing these different parts and having hands-on experience in using them gives you multiple starting points to deepening your knowledge. If you're interested in building controllers on the backend, you can learn more about that. If you wanna get better at CSS animations, you can learn more about that. Now you know that they exist so it's easier to learn more about them.</p><h2 id="the-ground-up-approach">The ground up approach</h2><p>Another approach is to start from the ground up. This is the way I've taught in many university courses I've worked on. You learn about variables and spend some time modifying variables. Then you learn about if/else and spend time experimenting with different ways they can be used and combined. Then you move to things like loops, functions, classes, and other concepts.</p><p>Focusing on the basic concepts and having a solid base of understanding before moving forward gives you a great platform to learn more and to have a more comprehensive knowledge of the topics at hand. This is the way I learned my ropes of software development. I read a ton of books and had a desire to understand as much as possible before moving forward.</p><p>While this can be slower in the beginning (the time from start to having a deployed app can be very long), it can speed up your process down the line as you'll have more breadth in your knowledge base. You will be able to see different opportunities and options and avoid pitfalls because of this.</p><p>One thing I struggled with when learning programming this way was understanding how things connect to each other. I could write functions all day long but it took me a long time to understand how to build software beyond individual functions or classes.</p><h2 id="which-one-to-choose">Which one to choose?</h2><p>The thing is, one isn't necessarily better than the other. They are different approaches and I recommend choosing the one that makes you happy and keeps you learning. They will converge eventually as your journey matures: those who start by creating software will have to deepen their understanding of the basics and those who start from theory and basics will have to learn to build things.</p><p>One thing I strongly believe about learning is that staying motivated is the key. It's really difficult to keep on learning something if you don't see progress, if you're not enjoying what you're doing and if you just force yourself push it through. If you start with the hands-on approach and feel like you're just doing todo apps and want to understand more, try out the other approach, get some books or classes and start learning that way. If you're learning the basics but feel like it's not taking you anywhere, take a break from that and build something even if you don't fully understand and master all aspects of it.</p><p>If there's one advice I'll give to anyone new to programming:</p><p><strong>Pick a language/tool/framework/whatever and go with it.</strong></p><p>There are no right or wrong answers to "which language should I learn first?" Any language you learn will help you in your path and one of the things that can slow you down is jumping from one thing to another every time someone recommends their favorite language as the best one to learn first.</p><p>One metric I would use when thinking about one is the size and activity of the community: how many quality books, guides, tutorials, Stack Overflow questions and answers and so on there are. Because you will get stuck a lot. And it helps when there's someone you can find to get help.</p><p>And if you know specifically that you want to build a certain thing: a website, a mobile app or a game, you might want to pick a language and toolkit that's aimed for it. But other than that, anything goes.</p><p>Especially <a href="https://mobile.twitter.com/florinpop1705/status/1263816887819014145">in Twitter</a>, I see a lot of discussion and opinions on whether one can start from React or if they should first master basic Javascript. And while knowing only React won't get your far (as React is a Javascript framework), it's a perfectly valid starting point. Create an app or two or three with it, learn how to make things happen, learn how to deploy them to a cloud provider and as your app grows in complexity, you'll have to learn more and more Javascript to be able to continue developing it.</p>
Building Hobby Projects to Help Get Your First Programming Job (via honeypot.io)
2020-06-01T00:00:00Z
https://hamatti.org/posts/external-cult-build-hobby-projects-get-first-job/
<p>Read this article at <a href="https://cult.honeypot.io/reads/build-hobby-projects-get-first-job">.cult</a>.</p>
How to scrape a website with Python & BeautifulSoup
2020-05-27T00:00:00Z
https://hamatti.org/posts/how-to-scrape-website-with-python-beautifulsoup/
<p>There's a lot of data on websites in the Internet. Some data is provided via APIs (and I'm currently writing a Humane Guide for people who want to learn the fundamentals of calling APIs, more of that later) but quite a lot of data is just static and displayed in HTML across websites.</p><p>One could manually copy-paste the data into editor, do a lot of clean-up and then reuse it for computation but as developers, we have better tools for automating them. In this post, I'll share how you can scrape data from a website using <a href="https://www.python.org/">Python</a>, few libraries to fetch the data and <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">BeautifulSoup library</a> to parse the data.</p><p>This blog post is using Python 3.7.2.</p><h2 id="note-about-permissions">Note about permissions</h2><p>Just because a piece of data is publicly on a website, it does not mean that you can do whatever you want to retrieve and reuse it. I am not a lawyer and thus, I won't provide any advice here on legality of the tasks. Please consult legal experts on these issues before building bots that scrape the Internet.</p><h2 id="primer-to-beautifulsoup">Primer to BeautifulSoup</h2><p>BeautifulSoup is a Python library that parses XML and HTML strings and provides you access to the data in an easier format to consume in your applications and scripts. To get started, we need to <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-beautiful-soup">install BeautifulSoup</a>.</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">pip install beautifulsoup4
</code></pre><!--kg-card-end: html--><p>To make sure everything is working correctly, let's try to replicate the example from BeautifulSoup's documentation:</p><pre><code class="language-python">from bs4 import BeautifulSoup
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
for link in soup.find_all('a'):
print(link.get('href'))</code></pre><p>By saving the previous example in a Python file and running it, you should see the following output:</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">http://example.com/elsie
http://example.com/lacie
http://example.com/tillie</code></pre><!--kg-card-end: html--><p>So what happened here? First, we need to import BeautifulSoup (line 1). After that, we use some example HTML stored as a string in a variable called <code>html_doc</code> and provide that as a first argument to <code>BeautifulSoup()</code> constructor. The second argument is for defining the parser that you want to use. There are multiple parsers you can use and <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser">the documentation has a nice comparison table</a> for them.</p><p>After we've created a BeautifulSoup object, we can call methods on it to query different data. In this case, we call <code>soup.find_all</code> which allows you to <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/#searching-the-tree">search the tree </a>for elements based on arguments. Here we pass in the <code>'a'</code> to signify that we want all the anchor tags. We then loop over and print the <code>href</code> attribute of each anchor tag.</p><h2 id="searching-the-tree">Searching the tree</h2><p>BeautifulSoup provides us with different methods to search the tree. Let's take a look at two of them: <code>find</code> and <code>find_all</code>.</p><p><code>find</code> always searches and returns the first match. If in the previous example, we would have used <code>find</code> instead of <code>find_all</code>, we would have only gotten the first anchor tag rather than all three. <code>find_all</code>, like the name suggests, returns all matches.</p><p>First argument to these methods is called a <code>filter</code>. You can provide a string (like <code>'p'</code> to match paragraphs), a regular expressions (like <code>re.compile(r'h\d')</code> to match all headings (tags with h and any single digit)), a list of strings or regular expressions (like <code>['tbody', 'thead']</code>) , a <code>True</code> to match all tags (but not text content) and finally, a custom function which allows you to do more complex predicates.</p><p>In addition to searching for tags, you can provide these methods with keyword arguments to search & filter by different HTML attributes.</p><pre><code class="language-python">for sister in soup.find_all('a', class_='sister'):
print(sister)
# Prints
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
for link1 in soup.find_all('a', id="link1"):
print(link1)
# Prints
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
for example in soup.find_all('a', href=re.compile('.*example.com.*')):
print(example)
# Prints
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a></code></pre><h2 id="accessing-data-and-attributes">Accessing data and attributes</h2><p>Now that we know how to search specific elements, let's see how to access data within them.</p><pre><code class="language-python"># Let's take the first anchor tag
first_sister = soup.find('a')
# Access the tag name
print(first_sister.name) # Prints 'a'
# Access the HTML attributes
print(first_sister.attrs) # Prints {'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}
# Access the text content
print(first_sister.string) # Prints 'Elsie'
# You can also use (see below paragraph for difference between text and string)
print(first_sister.text) # Prints 'Elsie'</code></pre><p>On a simple example like this, <code>.text</code> and <code>.string</code> seem to do the same but there are some differences you should be aware of:</p><h3 id="-text">.text</h3><p><code>.text</code> returns a concatenated string of all the text content from all children nodes.</p><pre><code class="language-python">text_example = '<p>Hello <span>World</span></p>'
soup = BeautifulSoup(text_example, 'html.parser')
print(soup.find('p').text)
# Prints 'Hello World'</code></pre><h3 id="-string">.string</h3><p>On the other hand, <code>.string</code> returns a <code>NavigableString</code> object which allows us to search further using methods like <code>find</code> and <code>find_all</code>.</p><pre><code class="language-python">text_example = '<p>Hello <span>World</span></p>'
soup = BeautifulSoup(text_example, 'html.parser')
print(soup.find('p').string) # Prints None
print(soup.find('p').find('span').string) # Prints World</code></pre><p>It only returns the string content if there is a single or no children.</p><h3 id="-attrs">.attrs</h3><p><code>.attrs</code> returns a dictionary with all the HTML attributes present in the element. You can only access individual attributes directly by indexing the tag element like you would a dictionary:</p><pre><code class="language-python"># Let's take the first anchor tag
first_sister = soup.find('a')
# We can access href in three ways:
print(first_sister.attrs['href']) # Prints http://example.com/elsie
print(first_sister['href']) # Prints http://example.com/elsie
print(first_sister.get('href')) # Prints http://example.com/elsie</code></pre><p>Some HTML attributes allow you to have multiple values, like <code>class</code>. In these cases, accessing the attribute returns a list instead of a string.</p><pre><code class="language-python">html = '<div class="first second third">Hello world!</div>'
soup = BeautifulSoup(html)
print(soup.find('div').attrs['class']) # Prints ['first', 'second', 'third']</code></pre><p>This happens even if there is only one class:</p><pre><code class="language-python">html = '<div class="first">Hello world!</div>'
soup = BeautifulSoup(html)
print(soup.find('div').attrs['class']) # Prints ['first']</code></pre><h2 id="let-s-parse-a-real-website-">Let's parse a real website!</h2><p>Alright, I know you're probably eagerly waiting to leave these toy examples behind and parse something in the real world. Let's do that! For this example, I'm using my own website and especially its <a href="https://hamatti.org/speaking">/speaking page</a>.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/Screen-Shot-2020-05-23-at-9.37.45-PM.png" class="kg-image" alt="Screenshot of a website showing a table of conference talks with titles, event names and dates" /></figure><p>At the time of writing, the first table, <strong>conference talks, </strong>looked like the above screenshot.</p><p>Our goal is to parse all of my talks that have a link to a Youtube video and print them out.</p><p>To get access to that HTML, we first need to do a HTTP request to the server to get the data and for this, there are multiple ways to do it in Python. For static sites like this, I usually use <a href="https://requests.readthedocs.io/en/master/">requests library</a>.</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">pip install requests</code></pre><!--kg-card-end: html--><p>Now we can use <code>requests</code> to fetch the website and feed that into our BeautifulSoup</p><pre><code class="language-python">import requests
from bs4 import BeautifulSoup
URL = 'https://hamatti.org/speaking/'
page = requests.get(URL)
soup = BeautifulSoup(page.text, 'html.parser')
# If you get 'Speaking : hamatti.org', all is good!
print(soup.find('title').text) </code></pre><p>At this point, I usually open the DOM Inspector in my Developer Tools (you can also decide to View Source) to see what the structure of the site looks like and try to find items I'm interested in.</p><p>In this case, we have four different tables: <strong>Conference Talks, Meetup Talks, Podcasts </strong>and <strong>Startup Accelerators</strong>. The links to videos are in first column but not all talks have links.</p><pre><code class="language-python">tables = soup.find_all('table')
tbodies = []
for table in tables:
tbody = table.find('tbody')
tbodies.append(tbody)
first_columns = []
for tbody in tbodies:
trs = tbody.find_all('tr')
for tr in trs:
td = tr.find('td')
first_columns.append(td)
links = [td.a for td in first_columns if td.find('a')]
youtube_links = [link for link in links if 'youtube' in link['href']]
for link in youtube_links:
print(f'{link.text}: {link["href"]}')</code></pre><p>Parsing websites often end up in quite messy looking scripts. And website structure, classes and ids often change so if you're planning to use this kind of a script in an automated environment, be ready for some failing scripts and maintenance down the line.</p><p>My workflow with parsing the web is very iterative. I usually work in Python shell/REPL and work my way through the soup step by step with a lot of trial and error. Once I find something that works, I copy it to a script and at the end, clean it up a bit.</p><h3 id="dynamic-websites-and-working-with-javascript">Dynamic websites and working with Javascript</h3><p>Using <code>requests</code> is good when the website is static, as in the data is in when it gets returned from the server. However, sometimes that's not the case and websites start with empty divs and fill in the data by using API calls and Javascript. In those cases, you will receive the empty div when trying to use <code>requests</code> and <code>BeautifulSoup</code>.</p><p>For these cases, we need to use a headless browser that loads the website, renders it, runs Javascript and then gives us the resulting data. There are multiple options for that but one that is very popular is <a href="https://selenium-python.readthedocs.io/">selenium</a>. Selenium allows us to automatically run browser windows, navigate to websites, interact with fields and buttons and it executes the Javascript run on the page.</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">pip install selenium</code></pre><!--kg-card-end: html--><p>In addition to installing selenium itself, you also need to install drivers for the browsers you want to use. In this example, I'm running Firefox so I'd install <a href="https://github.com/mozilla/geckodriver/releases">geckodriver</a>. Selenium's documentations has information on different drivers and links to their installation pages.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/Screen-Shot-2020-05-23-at-10.40.34-PM.png" class="kg-image" alt="Screenshot of PokemonTCG.io page listing information of published card sets" /></figure><p>For this example, let's use the website of one of my favorite APIs, <a href="https://pokemontcg.io/sets">PokemonTCG.io</a>. When visited, the /sets subpage gives us a nice visual representation of different sets. </p><p><em>In reality, you would not scrape this particular website as they offer an open API to get all the same data. I'm just using it as an example of a website that injects data into DOM with Javascript.</em></p><p>If we would try to access those by using what we did earlier, we'd end up with empty results:</p><pre><code class="language-python">import requests
from bs4 import BeautifulSoup
URL = 'https://pokemontcg.io/sets'
resp = requests.get(URL)
soup = BeautifulSoup(resp.text, 'html.parser')
print(soup.find('div', class_='set-gallery')) # Prints None</code></pre><p>This is because after the site has loaded in browser, it calls API to get the sets data and then injects that into a <code>div</code> with class <code>set-gallery</code>.</p><p>Using Selenium gives us the power to run the website in a real browser, wait for it to load and then access the populated DOM.</p><pre><code class="language-python">import re
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from bs4 import BeautifulSoup
URL = 'https://pokemontcg.io/sets'
driver = webdriver.Firefox()
driver.get(URL)
elements = WebDriverWait(driver, 10).until(
expected_conditions.presence_of_all_elements_located((By.CSS_SELECTOR, '.card-image')))
html = driver.page_source
driver.quit()
soup = BeautifulSoup(html, 'html.parser')
sets = soup.find_all('a', class_='card', href=re.compile(r'/cards.*'))
for set_ in sets:
set_name = set_.find('p', class_='title').text
set_release_info = set_.find(
'p', class_='subtitle').text.replace('Released ', '')
print(f'{set_name} was released on {set_release_info}')</code></pre><p>First, we import our libraries. <code>re</code> and <code>BeautifulSoup</code> are already familiar from previous examples so let's take a look at what we import from selenium.</p><p>First, we need the <code>webdriver</code> as that will be used to run Selenium. We also import <code>WebDriverWait</code> which we can use to force Selenium to wait for certain actions before closing the browser. <code>By</code> allows us to access different ways of filtering HTML elements (see line 16), and finally <code>expected_conditions</code> is used to tell Selenium what conditions we want to be matched. Depending on what you're doing, you might not need all of these or you might need more. Reading the documentation helps you find out what you need.</p><p>On lines 11-12, we open Firefox and navigate to our URL. Then we tell Selenium to wait until elements with class <code>card-image</code> are present before closing the window. It took me a couple of attempts to find good elements to refer to make sure data in this use case was populated correctly.</p><p>After that, we get access to the HTML source and we close the browser.</p><p>Now, we do what we've done before: finding all sets (in this case they are <code>a</code> tags with certain classes and URLs), scraping the names and release info and doing some data cleanup before printing all of them into the standard output.</p><h2 id="wrap-up">Wrap-up</h2><p>We have now learned how to parse HTML into BeautifulSoup and use it to find the data we want. We also learned how to use <code>requests</code> and <code>selenium</code> to access the data on live websites. What's next? Time for you to experiment, write your own scripts, read more on these libraries' documentation pages and build something nice for yourself!</p>
Minimal Travel Table Top Game Collection 2: Social Distancing Edition
2020-05-25T00:00:00Z
https://hamatti.org/posts/minimal-travel-table-top-game-collection-social-distancing-edition/
<p>During my Christmas holiday, <a href="https://hamatti.org/posts/minimal-travel-tabletop-game-collection/">I created Minimal Travel Table Top Game Collection</a>. The idea was to build a box with a set of games based on cards and dice that I could fit into my backpack to carry with me on my day-to-day life and travels. Then things happened and traveling and playing board games become a bit more difficult.</p><p>So, one weekend being alone at home, I started to look back into the Print and Play game design community and especially games that are meant for a single player. And to stay true to the Minimal Travel theme, I wanted small games.</p><p>Lucky for me, BoardGameGeek hosts plethora of different table top game design contests and one of them is an annual <a href="https://boardgamegeek.com/thread/2339779/2020-9-card-game-print-and-play-design-contest">9 Card Game Print and Play Design Contest</a> where game designers and hobbyists come together to build interesting new games. Let's take a look at what I ended up with.</p><h2 id="the-contents">The Contents</h2><p>I started my box from the same premise as with my first MTTTGC: <a href="https://ultimateguard.com/en/deck-case/deck-case-80">an Ultimate Guard 80+ Deck Box</a>. I have these laying around and I think they are a great size for small card & dice game collections. They are small enough to challenge you to make difficult choices but fit so nicely into your pocket or backpack.</p><p>Last time all of my games were fitted to be single-sided so I used Dragon Shield's Red Matte sleeves. This time I wanted to try something new, so 60% of my games are still using these sleeves and are required to flip over when switching games. But I also wanted to experiment (and 18 card faces compared to 9 opens up so many possibilities for game mechanics and replayability) so two games are double-sided and use <a href="http://www.boardgamesleeves.com/sleeves/standard-non-glare">Arcane TinMan's clear board game sleeves</a> (although in pictures below, I didn't yet have those sleeves).</p><p>As many of these games use bit more additional pieces than just cards, I decided to go with 45 cards (5 sets of 9) and ended up with 8 games. I also have cubes and meeples in 6 colors (5 x 6 cubes, 5 x 2 meeples) + one smaller set of 2 cubes and meeples in black.</p><h2 id="the-games">The Games</h2><p>This time the pool of possible games was quite a lot more limited than last time. Solo games are much smaller niche than multiplayer games and while there are multiplayer games that can be played alone, I wanted to find games that are designed solo play first.</p><h3 id="switchboard">Switchboard</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/switchboard.jpeg" class="kg-image" alt="9 cards in 3x3 grid with dice and cubes on top" /></figure><p>My first game of the set is <a href="https://boardgamegeek.com/thread/2151233/wip-switchboard-solo-game-crossed-lines-2019-9-car">Switchboard - A Solo Game of Crossed Lines</a> which is a puzzle game designed by Mark Tuck for 2019 design contest where it finished 3rd in both the solo game and the overall category. It uses 9 single-side cards 12 cubes of 6 different colors and 3 D6 dice.</p><p>This game is a puzzle game with nice amount of randomness which means it will provide a great replay value, which is more important in solo games than multiplayer games which can gain a lot of replay value from other people.</p><p>You play as a telephone switchboard operator (I have to say, I've grown fond of this theme as I also enjoy <a href="https://plushpopsoft.itch.io/switchboard-operator">Switchboard Operator</a> game from 2018 Global Game Jam) who needs to connect a growing number of calls by swapping or rotating cards (that represent phone lines) or moving recipients around the edges. The game uses dice as a marker for both timer and scoring. Each operation costs valuable seconds and if you cannot connect all calls in round before you run out of time, you lose.</p><h3 id="mini-rogue">Mini Rogue</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/mini-rogue.jpeg" class="kg-image" alt="Cards, dice and cubes on table" /></figure><p><a href="https://boardgamegeek.com/boardgame/199242/mini-rogue">Mini Rogue</a> by Paolo Di Stefano and Gabriel Gendron is a representative of its category in this set. Turns out, building different kind of light role-playing games and dungeon crawlers is a very popular thing in this PnP Micro Solo Game world. I took a look at a dozen of them and decided to go with Mini Rogue.</p><p>It finished 2nd place in both solo game and overall game categories in 2016 9-Card Nanogame Design Contest and was a nominee for 2016 Golden Geek Best Print & Play Board Game awards and based on reviews and discussions, seemed to be the best of its kind in the dungeon crawler category.</p><p>Played with 9 single-side cards, 4 D6 and 8 tokens, Mini Rogue sends you fighting monsters in dungeons. It packs four different difficulty levels, branching dungeons, RPG stats with leveling up and boss fights into a very small package and is a delight to play.</p><p>One of the reasons I chose this game was how extensive they were able to make it with so few elements. Each card provides different challenges and rewards depending on the level of dungeon you are in and even though there's not unlimited amount of randomness, different randomized dungeon layouts provide different type of adventures.</p><p>Another game I considered at the end was <a href="https://boardgamegeek.com/thread/2371568/wip-8-bit-dungeon-2020-9-card-design-contest-conte">8-bit Dungeon</a> by Robert Lausevic that is part of currently on-going 2020 9 Card Game Print and Play Design Contest. Maybe I'll revisit that one after the contest is over.</p><h3 id="forest-guardians">Forest Guardians</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/forest-guardians.jpeg" class="kg-image" alt="cards and cubes on table" /></figure><p>Just look at those cute characters! <a href="https://boardgamegeek.com/thread/2349088/contest-ready-forest-guardians-2020-9-card-design">Forest Guardians</a> is a new game by Bunyol De Vent and is currently competing in the 2020 9 Card Game Print and Play Design Contest. It features incredible art and is played with 9 single-side cards and 8 cubes.</p><p>You are playing as three heroic mice who fight against evil cat knights in a very interestingly designed game that uses positions and level of health in a refreshing way. Each turn, you select one of your heroes to fight and attacks and damage are based on the positioning of enemies. Special abilities change as characters take damage and they for example move characters around.</p><p>So far, I'm not sure how much replay value this game offers in the long run. It might turn out that there won't be enough interesting combinations that will emerge from 6 enemies and 3 heroes but only time will tell that.</p><h3 id="12-patrols">12 Patrols</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/12-patrols.jpeg" class="kg-image" alt="9 cards in fan formation and some cubes and dice" /></figure><p>The next game on the list, <a href="https://boardgamegeek.com/boardgame/271270/12-patrols">12 Patrols</a> by Scott Allen Czysz, is a game I expect spending longest with from the games in this set. It offers interesting challenges in a lightweight puzzle type of set matching game. Played with 9 cards (+ 3 if you print the expansion), 9 D6 and 9 cubes, every game is a different one with plenty of randomization through cards and dice rolls.</p><p>In 12 Patrols, you're tasked with protecting the kingdom by placing 12 patrols of knights and soldiers around a 3 x 3 grid. The catch is, each card has requirements that must be met. For example, Wolven Forest requires you to have exactly 2 knights or soldiers to the north of it in the entire column. As you place the cards and patrols as you build the randomized kingdom, you have to be careful to not block your future moves by adding the wrong amount of patrols.</p><p>There are three type of requirements: sum of dice, amount of patrollers and the type combination of patrollers and they are all solved for the entire row or column. Not the type of puzzle I'm usually very good at and that was exactly the reason I picked this game. The artwork is also beautiful and nicely thematic. I will probably print the extension cards as well in the future to further expand the possible kingdoms but had to drop it from this one to fit the 45 card limit. If you're thinking about this game, go for the full 12 from the beginning.</p><h3 id="9-card-siege">9 Card Siege</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/9-card-siege.jpeg" class="kg-image" alt="cards, cubes and a die" /></figure><p><a href="https://boardgamegeek.com/boardgame/224596/9-card-siege">9 Card Siege</a> by Chris Hansen was developed for the 2017 9-Card Nanogame Print and Play Design Contest where it finished 2nd in the wargame category. It uses 9 single-side cards, 9 cubes and 1 D6 to build an experience of defending a medieval fortress that's under siege. I originally found the game through recommendations on the forum.</p><p>The game throws you into the middle of an intensive battle where you have to survive for 3 days fending off enemy attacks. Each day is divided into 7 turns and consists of advancing the enemy, playing cards and taking actions. </p><p>At this point, I haven't played a lot of 9 Card Siege yet so it's hard to give a fair review of the game. I have played a bit of <a href="https://boardgamegeek.com/boardgame/43443/castle-panic">Castle Panic</a> before and I'm eager to see how the format fits into a smaller game.</p><h3 id="zone-runners">Zone Runners</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/zone-runners.jpeg" class="kg-image" alt="cards, cubes and dice" /></figure><p><em>(I failed at printing these cards and the fonts in some of them got messed up so please notice that the original art is much better than my printed ones in the picture.)</em></p><p>I have to admit, I was bit struggling to fit the last single-sided spot. Many games use double-sided cards or just felt a bit off or unfinished to my taste. I ended up going with Zone Runners from 2017 9-card Nanogame PnP Design Contest where it won the solo category and finished 2nd in the overall category.</p><p>Designed by Mark Tuck (who also designed the Switchboard above), Zone Runners uses 9 single-side cards, 8 D6 and 9 cubes of 4 colors. It puts you into a TV game show from the future where contestants run in an obstacle course dodging mutants and avoiding gaps.</p><p>Your team of three runners have different abilities and based on the roll of dice, you assign them to runners in a way that helps your team overcome the challenges of the course and reach the end with maximum amount of credits.</p><h3 id="empire">Empire</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/empire.jpeg" class="kg-image" alt="cards, cubes and dice" /></figure><p>Todd Sanders' <a href="https://boardgamegeek.com/boardgame/282581/empire">Empire</a> is a 2019 9 Card Game Print and Play Design Contest game that finished 7th in the solo and 9th in the overall category. It's the first game in this list to use double-sided cards and adds to it 9 D6 and 9 cubes to build a game where you'll build an industrial empire by building factories, hiring workers and selling the goods to the market over a 30-year period of time.</p><p>I wanted to add a couple of games with bit more complex game play and rules to see how far the 9 card limitation could be taken. Empire was the first of these two and as a big fan of resource management games, I wanted to add it to the collection.</p><p>For each turn, you roll a die to set the market values, build buildings from randomly available selection, hire workers and produce goods. The game has simple and easy-to-learn mechanics but offers nice depth in the strategic decisions. The randomness of buildings available and market values at different times increases the replay value a lot as you cannot just rely on one single sequence of actions from one game to another to maximize your profit.</p><p>So if you're looking to build big industrial empire in small form factor, I recommend taking a look at Empire. If you have friends around and are more interested in larger games, I highly recommend <a href="https://boardgamegeek.com/boardgame/217195/east-india-company">East India Company</a> which requires players to be tabletop-savvy but offers great depth in building a profitable business together while making sure you're the individual who benefits most.</p><h3 id="count-of-nine">Count of Nine</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/05/count-of-nine.jpeg" class="kg-image" alt="9 cards in two rows" /></figure><p>Last but not least in this box, I have <a href="https://boardgamegeek.com/boardgame/247778/count-nine">Count of Nine</a> by Scott Allen Czysz (who also made 12 Patrols in this box). It finished 2nd place in both solo and overall game categories in 2019 9 Card Game Print and Play Design Contest.</p><p>Count of Nine is a euro-style city building game played with 9 double-sided cards. Each card represents 1 or 2 buildings that can be built as well as resources that it provides. At any point, you'll have an opportunity to build one of the buildings shown in the face-up cards (2-3 options at a time) and each card will only be available for a few turns per round. The game is scored by most victory points (minus rounds played) in 2 games.</p><p>It's an interesting game that forces you to make continuous decisions based on very limited resources. I feel like its sometimes bit too restricted and luck-based and you might really brick with the order of the cards you draw. The limitations are crucial for any resource management game but the way this game gets rid of your options on a quick rotation feels bit too volatile.</p><h2 id="conclusions">Conclusions</h2><p>It's been an interesting journey researching the world of solo micro/nano games. I'm excited to see how many different type of games people have been able to make within these restrictions. And it's been fun to read through the discussions of these games being developed and gaining incredible insights into the process of tweaking and balancing games based on game testing. And of course seeing how supportive the table top gaming community is.</p><p>I actually noticed only when writing this blog post that I ended up with two games by same designers. Mark Tuck's Switchboard and Zone Runners and Scott Allen Czysz's Count of Nine and 12 Patrols made the list but looking back, it's not a big surprise. Going down the funnel of table top design to print and play to 9 cards to solo games to top games shrinks the amount of designers quite small.</p><p>There's one game that didn't make the cut but that I really want to play. <a href="https://boardgamegeek.com/boardgame/273779/under-falling-skies-9-card-print-and-play-game">Under Falling Skies</a> by Tomáš Uhlíř pretty much swept the table in 2019 9 Card Game Print and Play Design Contest winning 5 categories and finishing 2nd in 3. Expanding on the base mechanic of video game classic Space Invaders, Under Falling Skies provides an interesting scifi card game that I'll have to add to the mix if I get bored with one of these in the list.</p><p>The quality of cards this time was much worse than in my first collection (totally my fault, not original designers or manufacturer). In the first collection, I recreated all cards from scratch which guaranteed a continuing artwork beyond bleed lines. This time, I took existing cards (on A4 PDFs) meant for print-and-cut and those did not include any bleeds. I tried my best to stretch the card images as far as I could but as a lot of them have crucial game play information on sides, this ended up with some white lines on some sides of some cards.</p><p>For a single player collection, that doesn't really matter but for the future builds, I'll take this into more consideration and spend more time recreating the artwork and copying the game mechanics to guarantee the cards look as slick as the gameplay is smooth.</p>
Tips for giving talks online
2020-05-20T00:00:00Z
https://hamatti.org/posts/tips-for-giving-talks-online/
<p>
As a public speaker, I have ended up doing a bunch of talks recently remotely.
Each time I think I've gotten a slight bit better and especially more
comfortable at talking to a webcam. As more and more events are now done
online and many people have to adjust to the situation, I'd share some of my
learnings here on my blog.
</p>
<p>
If you're a speaker (either experienced one or new in the field) during this
remote event era, I'd love to hear your tips
<a href="https://twitter.com/hamatti">in Twitter</a>.
</p>
<h2 id="a-friendly-face">A friendly face</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/05/blog-friendlyfaces.jpeg" class="kg-image" alt="Plush Totoro sitting partially behind a screen and a webcam pointed towards the camera." />
</figure>
<p>
15 minutes before any talk that I do, the butterflies find their way into my
stomach. This happens both in-person talks and remote talks but one thing that
has been very helpful to reduce it has been talking with people in the event
before my talk and then seeing those friendly faces in the audience.
</p>
<p>
During these remote events, it's bit harder to interact with the people (see a
tip for that below) and once you start talking, there are no more friendly
faces, only the logo of the company that made your webcam and a glooming green
light staring at you.
</p>
<p>
Something I learned years ago when interviewing over video calls was to add a
pair of eyes behind the webcam to make it more natural to take eye contact
with the camera rather than looking down to the screen.
</p>
<p>
Meet Totoro. I've brought Totoro into so many job interviews, meetings and
talks without anyone on the other side knowing. (Funny anecdote: first time I
talked about this, someone didn't realize I talked about video job interviews
and asked how the interviewers felt about me bringing a plush toy with me).
And it's so helpful. The friendly and cute face of Totoro helps me feel like
I'm talking to someone and even though he doesn't blink or nod in support, I
believe he would if he could.
</p>
<p>
If you don't have Totoro or can't ask a family member to sit behind the webcam
in support, you could use googly eyes attached to the webcam or build an
audience by cropping images of people from magazines and stick them into a
cardboard behind your webcam.
</p>
<h2 id="have-a-timer-in-a-second-screen-or-phone">
Have a timer in a second screen or phone
</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/05/timer.jpeg" class="kg-image" alt="A countdown timer on laptop screen" />
</figure>
<p>
Timing my talks has always been a challenge. In physical conferences where
I've spoken, there's usually been a clock or the MC showing timing cards in
the front row but at the leisure of your home, it can be easy to get
comfortable and not notice the passage of time. In physical events I also
often wear a fitness wristband set to vibrate quietly on a given time to give
me a heads up that certain amount of time has passed.
</p>
<p>
For virtual events, I use
<a href="https://www.tickcounter.com/timer">TickCounter</a> as it provides me
with a large countdown and I can easily just peek at it from the corner of my
eye to make sure I stay within the boundaries of my time. So far, all my
remote talks have finished within 20 seconds of the given time and using a
timer really helps in that.
</p>
<p>
It's also more challenging to notify the speaker about the time in a subtle
way because as a speaker I focus on watching the webcam. So be a good speaker
and make sure you stick to the time slot given to you.
</p>
<h2 id="double-check-your-tools">Double-check your tools</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/05/blog-mic-and-headphones.jpeg" class="kg-image" alt="a podcast microphone with headphones resting on top of it" />
</figure>
<p>
Another major challenge I see in online events is that I can't see what others
see. I click <strong>share screen</strong> and hope for the best. I'm very
paranoid of how badly the presenting goes so I take some extra caution to make
sure the experience is as smooth as possible.
</p>
<p>
I pretty much always use Keynote to create the slides for my talks and in a
physical event, I'll plug my laptop in and click Present. I can always double
check the situation by taking a look at the screen. When online however, after
I go full screen, I lose all visibility.
</p>
<p>
I don't trust that all video conference tools do full screen similarly so if I
decide to go that route, I usually do a test run either with the organizers or
with friends or colleagues before the event using the exact same setup of
tools and ask them to report what they see.
</p>
<p>
But even more robust approach I have started to take is to export my talks to
PDF, use PDF reader with all the extra toolbars hidden and only share the
specific window instead of entire screen (this also helps avoid accidentally
showing what's on your browser/desktop/other apps). This way I never go full
screen but the users still see just the slides. I currently use macOS's
Preview app but would love to find an app that allows me to hide even the top
title bar.
</p>
<p>
I also test my microphone before every talk. I use Yeti Blue mic and do
multiple runs of local testing: I record the mic with Quicktime Player and
play it back to myself to make sure it's working as intended. Another small
thing I do is make sure my video conference tools have any echo cancellation
or audio enhancement features turned off because at least Zoom thinks that me
speaking is something that needs to be enhanced away and using those (default,
may I add) features breaks my input constantly.
</p>
<p>
If you use laptop, make sure it's plugged in. Check your webcam to make sure
what's visible on the background. Fill your mug with a drink of your choice
and take a bathroom break before your talk.
</p>
<h2 id="bonus-following-the-stream-from-another-laptop">
BONUS: Following the stream from another laptop
</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/05/second-screen-1.jpeg" class="kg-image" alt="Laptop showing a conference talk video" />
</figure>
<p>
This is something that stems from my experience of streaming on Twitch and
might not be required in events where you have a host and someone who can talk
to you if things break. But I still do it when speaking in online conferences.
</p>
<p>
I use my second laptop connected to a separate network (in my case, hotspot
from phone) to avoid that stream causing issues on my presentation laptop or
the quality of its Internet connection. Seeing myself on the stream, even from
the corner of my eye helps me see how the conference production team has
decided to position my webcam, slides and so on and it gives me some peace of
mind.
</p>
<p>
For Twitch streaming where I'd talk and code live for hours, I would keep it
on my view all the time to make sure the stream doesn't break but for a
conference talk, I might glance it once or twice when taking a small pause
while switching slides and moving to a new topic.
</p>
<h2 id="book-me-to-speak-in-your-conference-or-meetup">
Book me to speak in your conference or meetup
</h2>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/05/blog-me-speaking.jpg" class="kg-image" alt="Juhis speaking to a microphone on stage" />
</figure>
<p>
I love talking about topics like documentation, debugging and learning that
are relevant to software developers but are not deep dives into technical
details. Some of my recent talks are
<a href="https://hamatti.org/talks/contemporary-documentation">Contemporary Documentation</a>
(PyCon Estonia 2019, Work From Home Conf 2020 + meetups),
<a href="https://www.youtube.com/watch?v=4eYi_iW-AQ4">Debugging Javascript</a>
(HelsinkiJS, Turku <3 Frontend, Munich Frontend Developers) and
<a href="https://hamatti.org/talks/i-teach-therefore-i-learn">I teach, therefore I learn</a>
(PyCon CZ 2019 + meetups).
</p>
<p>
You can find more about the talks and events from
<a href="https://hamatti.org/speaking/">my Speaking page</a>. If your event
could use a talk like these, send me an email at juhamattisantala@gmail.com
and let's talk if I can provide something of value to your community.
</p>
How to Get Overwhelmed in Tech (via honeypot.io)
2020-05-13T00:00:00Z
https://hamatti.org/posts/external-cult-how-to-get-overwhelmed-in-tech/
Dev Diary #1: Pokemon TCG Cube Draft
2020-05-13T00:00:00Z
https://hamatti.org/posts/dev-diary-1-pokemon-tcg-cube-draft/
<p>I've been a fan of developers and development teams who make and share content during their development. It's quite a bit more common in the gaming industry and <a href="https://www.youtube.com/playlist?list=PLydXikYr57Xyrq9tAu5MK1o1CX7qXZR8n">Prison Architect's development videos in Youtube</a> were a big reason why I got into that game.</p><p>At work as a developer, I have basically always worked on projects with varying levels of NDAs so I haven't been able to talk about what I've built. But nothing is stopping me from doing that for hobby projects. So I decided to give it a try: partly to write about what I'm working and partly to build some external accountability so I'm pressured to actually build this thing.</p><h2 id="i-m-building-a-tool-for-pokemon-tcg-online-cube-drafting">I'm building a tool for Pokemon TCG online cube drafting</h2><p>I have been playing Pokemon TCG somewhat actively since 2014, although mostly casually. And I've built a couple of tools for myself to make it easier to play test and build decks like <a href="http://proxymon.herokuapp.com/">Proxymon proxy printer</a> (not maintained) and <a href="https://pkmn-attack-damage.netlify.app/">Attack Damage Search</a>.</p><p>More recently, I've gotten into cube drafting. In cube drafts, players assemble a set of cards (usually around 600-800 cards) from their collections, combining old and new cards and create draft-able sets called cubes that they then draft with friends. Joe from Omnipoke made <a href="https://www.youtube.com/watch?v=biMmPol7fm0">a great video explaining cube drafting for new people</a> and <a href="https://www.youtube.com/watch?v=yJTommTdh78">Andrew Mahone has another introduction video</a>.</p><p>And now people are drafting online with lists of cards and then playing in different platforms like <a href="https://www.pokemon.com/us/pokemon-tcg/play-online/">the official Pokemon TCG Online</a>, <a href="https://untap.in/">Untap.in</a>, via video calls in Discord or Skype or through other imaginative solutions. But there's not very much tooling to help people draft. Hence, I decided to build one.</p><h2 id="how-would-it-work">How would it work?</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/04/draft-wireframe.png" class="kg-image" alt="Wireframe for drafting process" /></figure><p>Even though I've been on/off prototyping individual parts of the whole over the last month or so now, I don't yet have anything that would do anything. Above you can see a first sketch on what I think the drafting view itself should look like: you'll see the cards that are available for picking in the main view, a button to confirm your selection and a collapsable sidebar that shows all the cards you've picked, categorized by type.</p><p>Right now my plan is to separate the creation of a draft (settings, invitations, card pool, etc) and the actual drafting process. For the real-time multiplayer draft, I'm thinking about using WebSockets as it enables me to hopefully build a snappy experience. Whatever that means.</p><p>In this project, like pretty much all projects, my big challenge is scoping. There are so many things I'd like it to do but I also want something up and running as fast as possible so I can put it on the hands of other players for real life testing.</p><p>For the first version, I expect there to be a possibility to create a draft by importing a list of cards, inviting players to it and picking cards booster-by-booster and finally export the list to formats supported by proxy printers, PTCGO and Untap.in so that people can then play after drafting.</p><p>In future Dev Diary posts, I'll update you on my process and write about a certain point of view or topic.</p>
<code>blocks
2020-05-06T00:00:00Z
https://hamatti.org/posts/codeblocks/
<p><em>I've kept this original blog post here for history's sake but I've deleted the codeblocks repository and I do not recommend doing what I've done here. I have since fixed the problems on the source and learned that this approach caused more problems than it fixed and made the code examples on my blog inaccessible.</em></p><p><a href="https://hamatti.org/posts/how-my-site-is-built-with-eleventy-ghost/">My blog is built using Ghost as a headless CMS and Eleventy as a static site generator.</a> My main reason for switching the content side to Ghost was to gain access to a nice editing experience. And while I thoroughly enjoy writing again, adding code snippets into my blog posts is bit of a pain.</p><p>As an example, take a look at the following code snippet:</p><pre><code class="language-html"><nav class="menu">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav></code></pre><p>On a surface, it looks simple and easy. But under the hood, there are a couple of things I need to do to make it look like that for you as a reader.</p><h2 id="adding-classes-for-prism">Adding classes for Prism</h2><p>I use <a href="https://prismjs.com/">Prism.js</a> on my website to make these code blocks nicer looking. It adds syntax highlighting and line numbers. It is a major improvement for readability and requires me to add <code>pre</code> and <code>code</code> tags with specific class names in the HTML block in Ghost.</p><p>For a single snippet, it's not much. But having multiple snippets like I had in <a href="https://hamatti.org/posts/true-true-true-true-true-true-in-python/">my recent Python tuple blog post</a>, manually crafting these blocks becomes a bit of a chore - a one that I'm not specifically excited about. It introduces friction to my writing process and as I'm not a great writer to begin with, any added friction makes writing a less enjoyable experience and I end up making less technical blog posts.</p><p>So to make the above snippet nice, my script adds <code><pre class="language-html"><code class="language-html"></code> in the beginning and matching closing tags at the end of the script to trigger Prism.</p><h2 id="converting-special-characters-to-html-entities">Converting special characters to HTML entities</h2><p>To make sure that special characters used in HTML are displayed correctly, I need to convert them to HTML entities. That's even more of a chore than adding the surrounding tags and classes.</p><p>In the above example, there are 16 <code><</code> characters and 16 <code>></code> characters that need to be converted. Converting those manually is horrible, especially if you notice later you need to make a change on the code.</p><h2 id="introducing-codeblocks">Introducing <code>blocks</h2><p>So after mentioning this pain point to a friend in Slack as we discussed using Ghost, I figured that it's not <strong>that difficult </strong>to solve with my programming abilities. As a pythonista, I started a new Python project and wrote a script that gets the language name (used in class names) as a positional argument and reads the code snippet from standard input.</p><pre><code class="language-python">import sys
try:
language = sys.argv[1]
except IndexError:
print('Usage: codeblock.py [language] < [input file]')
sys.exit(1)
ENTITIES = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;'
}
codeblock = f'<pre class="language-{language}"><code class="language-{language}">'
for line in sys.stdin:
for character, encoded in ENTITIES.items():
line = line.replace(character, encoded)
codeblock = f'{codeblock}{line}'
codeblock = f'{codeblock}</code></pre>'
print(codeblock)</code></pre><p>I took the entity list from <a href="https://developer.mozilla.org/en-US/docs/Glossary/Entity">this MDN page of reserved HTML characters</a>. In essence, the script just reads from standard input, goes through line-by-line and replaces all instances of HTML entities, wraps it in HTML tags and prints the result to standard output where it can be copied and pasted into the blog post in Ghost.</p><p>There are a couple of ways to use it. If your code is already in a file, you can do <code>python codeblock.py python < code.py | pbcopy</code> (leave out <code>pbcopy</code> if you're not using a MacOS) and you have the modified thing in your clipboard. If your code isn't in a file, you can just do <code>python codeblock.py python | pbcopy</code> and write or paste the code snippet into standard input and use <code>CTRL-D</code> to signify end of input.</p>
True, True, True == (True, True, True) in Python
2020-04-30T00:00:00Z
https://hamatti.org/posts/true-true-true-true-true-true-in-python/
<p>A friend shared this interesting piece of Python code in our Telegram chat today:</p><!--kg-card-begin: html--><pre class="language-python"><code class="language-python">>>> True, True, True == (True, True, True)
(True, True, False)</code></pre><!--kg-card-end: html--><p><em>(If you want to figure it out yourself first, stop reading now and return once you want to learn or confirm your thoughts.)</em></p><p>So what's happening here? Let's first take a look at what are <a href="https://docs.python.org/3.7/library/stdtypes.html?highlight=tuple#tuple">tuples</a> in Python.</p><blockquote>Tuples are immutable sequences, typically used to store collections of heterogeneous data (such as the 2-tuples produced by the <a href="https://docs.python.org/3.7/library/functions.html#enumerate"><code>enumerate()</code></a> built-in). Tuples are also used for cases where an immutable sequence of homogeneous data is needed (such as allowing storage in a <a href="https://docs.python.org/3.7/library/stdtypes.html?highlight=tuple#set"><code>set</code></a> or <a href="https://docs.python.org/3.7/library/stdtypes.html?highlight=tuple#dict"><code>dict</code></a> instance).</blockquote><p>There are a couple of ways to create a tuple, two of which are seemingly used in this little brainteaser:</p><!--kg-card-begin: html--><pre class="language-python"><code class="language-python">>>> True, True, True # Using commas
(True, True, True)
>>> (True, True, True) # Using parenthesis
(True, True, True)
>>> tuple(True, True, True) # Using tuple constructor</code></pre><!--kg-card-end: html--><p>So when looking at the original piece of code, it seems that we're creating two tuples with three <code>True</code> in each and then comparing these items to each other. When comparing tuples, comparison is done item-by-item: first you compare items in index 0, then in index 1 and so on.</p><p>In reality, comparing two tuples however doesn't return a new tuple with the results of individual comparisons but a single boolean value <code>True</code> or <code>False</code>.</p><p>So what's actually happening when this code gets executed? To create a tuple, Python will evaluate each of its items and then store that data in a tuple.</p><!--kg-card-begin: html--><pre class="language-python"><code class="language-python"># First Python sees there's a True
# Then there's another True with comma in between
# Then there's expression True == (True, True, True) which gets evaluated into False
# A tuple is created with values (True, True, False)
>>> True, True, True == (True, True, True)
(True, True, False)
</code></pre><!--kg-card-end: html--><p>The comparison function, the use of tuples and the use of booleans is what makes this initially weird looking. If we take a look at an example of tuple creation with other values and operators, it becomes clearer.</p><!--kg-card-begin: html--><pre class="language-python"><code class="language-python">>>> 1, 2, 5 + 7
(1, 2, 12)
</code></pre><!--kg-card-end: html--><p>What happens here is exactly the same: each item is evaluated one by one, left to right and then stored as a tuple.</p><p>Thanks to <a href="https://twitter.com/helioloureiro">Helio Loureiro</a> for sharing this with me.</p>
Morning Coffee Projects: Youtube extension
2020-04-18T00:00:00Z
https://hamatti.org/posts/morning-coffee-projects-youtube-extension/
<p>We are living at the mercy of recommendation algorithms everywhere online these days. Social media (like Facebook, Twitter and LinkedIn), video platforms (like Youtube and Netflix) and e-commerce (like Amazon) all provide you some sort of filtered feed based on what their system thinks you'd like.</p><p>I'm a heavy user of Youtube but over time, their algorithms have started to feel like they work really bad. I get the same content recommended over and over again and they even recommend me videos I just saw the same or the previous day. Another thing I'm not a fan of is how much they recommend old videos (often ones I've already seen).</p><p>So a while ago I started to tinker a bit to build a Chrome extension for my own use to help me get less distracted about the old videos. You can find the code in my GitHub repository: <a href="https://github.com/Hamatti/blur-old-youtube-videos">https://github.com/Hamatti/blur-old-youtube-videos</a>.</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">const blurVideos = () => {
const videoItems = Object.values(
document.querySelectorAll("#content")
).filter((node) => node.classList.contains("ytd-rich-item-renderer"));
videoItems.forEach((videoNode) => {
const metaBlock = videoNode.querySelector("#metadata-line");
const dateBlock = metaBlock?.children[1];
if (dateBlock?.innerText?.includes("year")) {
videoNode.style.filter = "grayscale(1) blur(7.4px)";
}
});
}</code></pre><!--kg-card-end: html--><p>The core of the program is function <code>hideVideos</code> above. The video thumbnails in Youtube's front page (at least at the time of writing) are inside elements with ids of <em>content</em> and the ones with a class <em>ytd-rich-item-renderer </em>are actual video items.</p><p>We then find all the ones with the text <em>"year" </em>in them (meaning, I want to hide recommended videos that are older than 12 months) and I add inline styling with grayscale and blur to make them unreadable enough so I won't accidentally get excited about them.</p><p>In addition to the main functionality, I found a way (<a href="https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom">from Stack Overflow</a>) to monitor the DOM for any new nodes so as the latest addition, I added the code to be triggered every time that happens. This way, when you scroll down the never-ending scroll, the extension will automatically blur all the old videos from the new recommendations.</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">const observeDOM = (function () {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function (obj, callback) {
if (!obj || !obj.nodeType === 1) return; // validation
if (MutationObserver) {
// define a new observer
const obs = new MutationObserver(function (mutations, observer) {
callback(mutations);
});
// have the observer observe foo for changes in children
obs.observe(obj, { childList: true, subtree: true });
} else if (window.addEventListener) {
obj.addEventListener("DOMNodeInserted", callback, false);
obj.addEventListener("DOMNodeRemoved", callback, false);
}
};
})();</code></pre><!--kg-card-end: html--><p>This extension needs to be manually activated by clicking the extension button. Maybe later I'll add a way to toggle this on/off, right now the page needs a hard refresh. The way Chrome extensions does that is by adding a listener to <code>chrome.runtime.onMessage</code> and checking for <code>request.message === "clicked_browser_action"</code>.</p><p>After building this, I've been very happy browsing Youtube. I'm also shocked by how many of the videos Youtube recommends to me are older than one year (especially given that hundreds of hours of content is uploaded every day). Every now and then, over 75% of the videos in my feed are old and ones I have already seen. With the amount of videos I watch, I would have guessed it could have been quite easy to show <em>similar</em> videos rather than the same videos.</p><p>If you want to install and test it out, you can download the code from <a href="https://github.com/Hamatti/blur-old-youtube-videos">its GitHub repository</a> and <em>Load unpacked </em>extension in <a href="chrome://extensions/">chrome://extensions/</a>. The extension is open source and comes with no guarantees so feel free to play around. Let me know in <a href="https://twitter.com/hamatti">Twitter</a> if you have any feedback for it or fork the project in GitHub and make a pull request with any improvements!</p>
8 tech podcasts I listen
2020-04-08T00:00:00Z
https://hamatti.org/posts/8-tech-podcasts-i-listen/
<p>Podcasts are a great source of learning and staying up to date on what's happening in tech. I listen to a bunch of them during my commute or when taking a walk. Here are the podcasts aimed towards software developers that I listen to.</p><h2 id="podcasts-in-english">Podcasts in English</h2><p><a href="https://ladybug.dev/">Ladybug Podcast</a></p><p>The breadth of topics that Kelly, Emma & Ali go through in Ladybug podcast makes it a great listen every time. In a short time, they have talked about design, interviews, management, data structures and algorithms, game dev and plenty of other topics.</p><p><a href="https://syntax.fm/">Syntax.fm</a></p><p>Wes and Scott might be the most dynamic duo in the tech podcast sphere. I'm a huge fan of Wes' courses and listening to these two talk about anything and everything tech related is a blast.</p><p><a href="https://softskills.audio/">Soft Skills Engineering</a></p><p>Dave and Jamison have a different kind of podcast. It's made for developers but they solely focus on non-technical skills in this fun and light-hearted discussion where they answer questions from the audience.</p><p><a href="https://www.redhat.com/en/command-line-heroes">Command Line Heroes</a></p><p>Redhat's Command Line Heroes is a podcast about people who've built the tech we're all relying on. It's about stories and biographies of people and technologies and Saron does a brilliant job telling these stories. If you're into tech and want to learn about the history of our great industry, this is a must listen.</p><p><a href="https://frontendhappyhour.com/">Front End Happy Hour</a></p><p>A great panel of hosts, drinking game and stories from the tech companies in Silicon Valley: welcome to Front End Happy Hour. Every episode they select a keyword and whenever its said, everyone takes a sip of their favorite beverage. And the topics vary from working environments to management and open source to specific technologies.</p><h2 id="podcasts-in-finnish">Podcasts in Finnish</h2><p>There aren't too many podcasts in Finnish but here are the ones I actively listen. If you don't understand Finnish, you can skip these.</p><p><a href="https://webbidevaus.fi/">Webbidevaus.fi</a></p><p>Webbidevaus is a podcast hosted by two web developers, Antti and Riku, who talk about different topics around web development and often have guests from the local scene in Finland.</p><p>I visited the podcast last September to <a href="https://webbidevaus.fi/56">talk about documentation</a>.</p><p><a href="https://audioboom.com/channels/5016335">Koodikahvit</a></p><p>Koodikahvit is the newest podcast on this list as it started in March of 2020. Pauliina and Anniina talk about everything related to IT industry and software development.</p><p><a href="https://koodi.info/">Koodi.info</a></p><p>Koodi.info is another new podcast that started in the beginning of 2020. UI developer Andreas Koutsoukos discusses different topics in software development, like JAMStack and career stories.</p>
Testing lifehack: testlab repositories
2020-04-01T00:00:00Z
https://hamatti.org/posts/testing-lifehack-testlab-repositories/
<p>I want to write better code (and code that in fact works) and writing unit tests helps me a bit in that. However, I'm not very good at setting up testing environments in existing projects. I have worked on some older legacy codebases where to test an individual part of code would have required a complex database and a lot of mocked data because of how the code was written originally.</p><p>One time I was working on a project like this, I still wanted to make sure that the functions I wrote were doing the right thing so I set up a new repository on my development machine called <code>php-testlab</code>. There, I set up a system where I would feel comfortable writing unit tests and knew how to run them with a single command.</p><p>Whenever I needed to work on fixing a bug or writing new functionality related to a specific part of that codebase, I would use <a href="https://www.freecodecamp.org/news/test-driven-development-what-it-is-and-what-it-is-not-41fa6bca02a2/">TDD</a> process to build that functionality inside my testlab repository and once I was reasonably certain it worked, I would copy it over to the actual codebase.</p><p>It wasn't the best way as those tests didn't end up in the project but at least the new code was tested during development and it increased the quality of the code I produced. Some time passed and I ran into continuing development of another legacy project. Now I had everything already set up with my testlab repository and I was able to start continuing the same thing.</p><p>Later, I did a similar thing for Python called <code>python-testlab</code> (innovative naming, I know!) where I had a setup for writing and running unit tests in Python.</p><p>Would I recommend others to follow this way? Kinda yes. It's not a real solution to the problem. Writing those tests into the project is. But sometimes life is messy and if you're able to increase your confidence in your code and reduce the anxiety of writing that code, I call that a win.</p><h2 id="my-php-testlab-setup">My PHP-testlab setup</h2><p>My <code>php-testlab</code> has a couple of files:</p><p><code>composer.json</code></p><!--kg-card-begin: html--><pre class="language-json"><code class="language-json">{
"autoload": {
"classmap": [
"src/"
]
},
"require-dev": {
"phpunit/phpunit": "^7"
},
"scripts": {
"test": "./vendor/bin/phpunit"
}
}</code></pre><!--kg-card-end: html--><p>And then in <code>tests/</code> folder I have file for each functionality as a separate test suite.</p><pre><code class="language-php"><?php
use PHPUnit\Framework\TestCase;
class HelloWorldTests extends TestCase {
function test_hello_world() {
$expected = 'Hello world';
$result = greetings('world');
$this->assertEquals($result, $expected);
}
}</code></pre><p>Now I can run my test with <code>composer test tests/HelloWorldTests</code>.</p><h2 id="my-python-testlab-setup">My python-testlab setup</h2><p>For Python, I have setup <a href="https://virtualenv.pypa.io/en/latest/">a virtualenv environment</a> so that I can manage packages when needed. After that, I have test files in the root like this:</p><p><code>test_string_methods.py</code></p><pre><code class="language-python">import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()</code></pre><p>Now I can run these with <code>python -m unittest test_string_methods.py</code>.</p>
Helpot askeleet videopuheluun
2020-03-25T00:00:00Z
https://hamatti.org/posts/helpot-askeleet-videopuheluun/
<p>Meillä tietotyöläisillä videopuhelut ovat alkaneet olla arkea jo viimeiset vuodet, mutta tavalliselle ihmiselle niiden käyttö ei ole ollut erityisen tarpeellista: monesti on voinut soittaa ja mennä kahville, jos on halunnut nähdä perhettä tai tuttaviaan. Nyt kun koko kansa istuu kotona eristyksissä, perheen tai ystävien väliset videopuhelut voivat olla erittäin arvokkaita.</p><p>Siksi ajattelin kirjoittaa tämän lyhyen oppaan siihen, miten kuka tahansa voi luoda ryhmävideopuhelun ja kutsua siihen haluamansa ihmiset ilman että tarvitsee asentaa mitään uusia ohjelmia tai luoda tunnuksia.</p><h2 id="jitsi-meet">Jitsi Meet</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/jitsi.jpg" class="kg-image" /></figure><p>Palvelu, jota näissä ohjeissa käytetään on Jitsi Meet - ilmainen ja erinomaisen hyvin toimiva videopuhelupalvelu. Sen löytää osoitteesta <a href="https://meet.jit.si/?lang=fi">https://meet.jit.si</a>.</p><p>Käydään läpi muutama askel miten saat hommat käyntiin:</p><h3 id="kielivalinta">Kielivalinta</h3><p>Jos haluat vaihtaa käyttöliittymän kieltä, se onnistuu oikean yläkulman ratas-nappulasta. Kun valitsee avautuvasta ikkunasta viimeisen välilehden (suomeksi Lisää, englanniksi More), löytyy pudotusvalikko, josta voi valita kielen.</p><p>Huomaathan, että käännöstyö ei ole ihan kaikenkattava, joten muilla kielillä kuin englanniksi saattaa osa teksteistä olla jäänyt kääntämättä.</p><h3 id="uuden-puhelun-luominen">Uuden puhelun luominen</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/aloita-uusi.jpg" class="kg-image" /></figure><p>Etusivulla on valkoinen tekstikenttä otsikon <strong>Aloita uusi kokous </strong>alla. Siinä pyörii esimerkkinimiä. Aloitat uuden puhelun kirjoittamalla siihen haluamasi nimen. Tämä nimi muodostaa uuden puhelun nimen ja näkyy osoitteessa, jonka jaat muille kutsuessasi heidät mukaan. Nimessä ei saa olla välilyöntejä.</p><p>Painamalla sinistä <strong>Aloita</strong>-nappia, pääset puheluun.</p><h3 id="kameran-ja-mikrofonin-k-ytt-lupa">Kameran ja mikrofonin käyttölupa</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/permission.jpg" class="kg-image" /></figure><p>Jotta Jitsi Meet voi näyttää kuvaasi ja jakaa ääntäsi muille osallistujille, pitää sinun antaa lupa selaimelle. Google Chrome -selaimessa tulee ensimmäisellä käyttökerralla yllä olevan kuvan näköinen laatikko, josta valitaan Allow.</p><h3 id="salasanasuojaus">Salasanasuojaus</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/password.jpg" class="kg-image" /></figure><p>Voi olla, että haluat suojata puhelusi salasanalla, jotta kuka tahansa linkin saanut (tai arvannut) ei pääse puheluun mukaan. Tämä tapahtuu klikkaamalla oikean alakulman Info-nappia.</p><p>Klikkaamalla <strong>Add password, </strong>kursori siirtyy Password-kenttään, johon voit kirjoittaa salasanan ja painaa enteriä. Tämän jälkeen, kuka tahansa puheluun liittyvä joutuu syöttämään salasanan ennen kuin pääsee liittymään.</p><h3 id="muiden-kutsuminen-ja-puheluun-liittyminen">Muiden kutsuminen ja puheluun liittyminen</h3><p>Ihmisten kutsuminen puheluun tapahtuu jakamalla linkki. Voit kopioida linkin joko selaimesi osoitepalkista tai inforuudusta, joka avautuu alakulman Info-nappulasta (ks. kuva salasanan asettamisesta).</p><p>Puheluun liittyvien tulee antaa selaimelle samalla tavalla oikeus mikrofoniin ja kameraan, jotta voitte kuulla toisianne.</p><h3 id="jitsi-meetin-k-ytt-">Jitsi Meetin käyttö</h3><p>Peruskäyttöön ei tarvitse tehdä mitään muuta. Liittymisen jälkeen voit alkaa keskustella. Usein on kuitenkin hyvä asettaa oma nimesi, joka näkyy kuvan päällä. Tämä on erityisen hyvä, jos keskustelet ryhmässä jossa kaikilla ei ole kameraa tai jossa ihmiset eivät tunne toisiaan kasvojen perusteella.</p><p>Nimi asetetaan oikean alalaidan kolmesta pisteestä avautusta valikosta. Ylin vaihtoehto, <em>me</em>, avaa pienen ruudun, jonka ensimmäiseen ruutuun kirjoitat oman nimesi. Tämä nimi näkyy muille puhelussa oleville.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-25-at-2.47.04-PM.png" class="kg-image" /></figure><p>Alapalkissa on kuitenkin muutama nappula, jotka voivat olla hyödyllisiä. Vasemman puoleisin, tietokonenäytön näköinen nappula antaa mahdollisuuden jakaa oma näyttösi. Käden kuva toimii kuin viittaaminen koulussa: kuvasi viereen tulee pieni sininen käsi, joka antaa vinkin siitä, että haluat puhua. Tämä on lähinnä tarpeellinen isommissa kokouksissa. Viimeinen vasemman ryhmän ikoneista, puhekupla, avaa tekstichatin.</p><p>Keskellä olevista ikoneista mikrofonin kuva hiljentää mikrofonisi, punainen luuri katkaisee yhteyden puheluun ja kameran kuva piilottaa kuvasi. Uudelleen painamalla mikrofonia ja kameraa kuva ilmestyy jälleen.</p><p>Oikean laidan ikoneista neljä neliötä vaihtaa näkymää, jossa puheluun osallistujat näytetään. Infonappulasta löytyy tarvittavat tiedot ihmisten kutsumiseksi (osoite ja salasana). Kolme pistettä avaa valikon, josta löytyy erilaisia lisätoimintoja ja asetuksia, joihin en mene tässä syvällisemmin.</p><p>Mukavia keskusteluhetkiä!</p>
Building a website with a static site generator, part 3: Domain, Analytics and Forms
2020-03-25T00:00:00Z
https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-3-domain-analytics-and-forms/
<p>In this series:</p><ul><li>Part 1: <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/">Building a website with a static site generator, part 1: Setup</a></li><li>Part 2: <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-2-eleventy/">Building a website with a static site generator, part 2: Eleventy</a></li><li>Part 3: Building a website with a static site generator, part 3: Domain, Analytics and Forms</li></ul><p>In the first post in this series we set up our static site with Eleventy and built a deployment process with GitHub and Netlify and added Netlify CMS for managing our content. In the second post, we learned how to integrate HTML5Up layout and added new content types to our site.</p><p>In this post, we look at setting up a domain, simple analytics and collecting data with forms.</p><h2 id="domain">Domain</h2><p> When you create a new site in Netlify, it randomly generates you a <code>[something].netlify.app</code> URL. It works and sometimes it's enough, especially for prototype projects. But for a personal website, blog or portfolio, you probably want something bit more personal and bit more professional.</p><h3 id="custom-netlify-app-domain">Custom netlify.app domain</h3><p>First step you can take to make it more personal and easier to remember is to go to <code>Settings->Domain Management->Custom Domains->Options->Edit site name</code> and give it your site a custom name. That way you'll get a free <code>[custom_name].netlify.app</code> domain.</p><h3 id="custom-domain">Custom domain</h3><p>For a full custom domain, you have a couple of options. The easiest one for this setup is to get your domain directly from Netlify. When you do that, you can manage everything from one place and you don't have to do any manual DNS settings.</p><p>You can buy a domain from the same place you change your site name (see above). Click <code>Add custom domain</code> and enter the domain you want. If it's available, you can buy it directly from Netlify with your credit card for an annual fee. If it's not available and you don't own it from another provider, you have to think about a new one. If you do own it somewhere else, you will be given an opportunity to add it to your Netlify site.</p><p>Their <a href="https://docs.netlify.com/domains-https/custom-domains/">documentation</a> is pretty good at walking you through all these different options.</p><p>I have domains both with Netlify and with <a href="https://www.hover.com/">Hover.com</a> and so far I've been happy with both.</p><h2 id="analytics">Analytics</h2><p>For analytics, you have multiple options depending on what kind of data you want to gather. For many of my sites, I don't collect any data. For my main site, I use Netlify's own analytics tools which is nice as it relies on server logs rather than collecting everything you can from individual users.</p><h3 id="netlify-analytics">Netlify Analytics</h3><p>Analytics in Netlify is the first paid feature covered in this post. It's not necessary for your site so while I wanted to keep this tutorial mostly free, I figured I'd mention it as well. At the time of writing, adding Netlify Analytics costs you $9/mo and you can enable them from the Analytics tab in your project.</p><p>Through their analytics, you get page views, unique visitors, top pages, top sources and the amount of bandwidth used. I've added it to my site mostly out of curiosity: I speak in a lot of events and it's always fun to see if any of those talks leads to a peak in the amount of visitors (spoilers: so far it hasn't).</p><h3 id="google-analytics">Google Analytics</h3><p>If you want to use a 3rd party analytics service or gain more insights, one of the most popular ones is <a href="https://analytics.google.com/analytics/web/">Google Analytics</a>. After creating a project for your site in their site, you need to add a piece of Javascript into your <code>base.njk</code> to add tracking to every page.</p><h3 id="fathom-analytics">Fathom Analytics</h3><p>Another option for Google Analytics that has been popping up on my radar in Twitter is <a href="https://usefathom.com/">Fathom</a>. I have never used it so I can't really say anything one way or another but many people seem to like it as an option to giving your users data to Google.</p><h2 id="forms">Forms</h2><p>If you want to collect some data like have a "Contact me" form or a feedback form on your site, <a href="https://docs.netlify.com/forms/">Netlify makes that pretty much as simple as possible</a>. By adding <code>netlify</code> attribute into your form, you automatically enable collecting data that you can then read from Netlify's admin panel.</p><!--kg-card-begin: html--><pre class="language-html"><code class="language-html"><form name="contact" netlify>
<p>
<label>Name <input type="text" name="name" /></label>
</p>
<p>
<label>Email <input type="email" name="email" /></label>
</p>
<p>
<button type="submit">Send</button>
</p>
</form></code></pre><!--kg-card-end: html--><p>There are two important attributes in this form: <code>name="contact"</code> which defines under which name your form submissions can be found in Netlify admin and <code>netlify</code> which enables the feature in the first place.</p><p>After you deploy this, when someone submits the form, you can find the results immediately from your admin page.</p><p>Spam is always a factor with online forms. There are bots that crawl the internet and fill in any forms they can find. To prevent them, you can either use a honeypot field (a hidden field bots will fill but humans won't) or reCAPTCHA2 which you might be familiar as a user. Read more from Netlify's <a href="https://docs.netlify.com/forms/setup/#spam-filtering">documentation on preventing spam</a>.</p><p>The free Netlify forms functionality allows you to gather up to 100 submissions per month across all forms in your site and file uploads up to 10MB per month.</p><h2 id="wrap-up-">Wrap up!</h2><p>With these three posts, you now have the basic understanding on how to get started building static sites with Eleventy, hosting it at Netlify (with extra goodies like domains and forms), managing your content via Netlify CMS and storing your code and its changes in GitHub.</p><p>Next is the hard part: you have to come up with good content and find your own tone and visual style. That's work that never ends. I spend time every month with my website, honing it, testing different things, and changing layouts and copy.</p><p>Having a personal blog is one of the most effective ways you can showcase the world what you can do. Write posts about projects you've done, things you have learned and so on. Don't stress too much, just get into the habit of writing as that's the only way to become good at it.</p><p>Some of my most popular blog posts over the years have been <a href="https://hamatti.org/posts/php-needs-its-own-es6/">PHP needs its own ES6</a> in which I ranted about my frustrations towards the inconsistencies of PHP's standard library and <a href="https://hamatti.org/posts/i-love-writing-small-scripts/">I love writing scripts to solve small problems</a> in which I wrote about a small script that I wrote to automate renaming files.</p><p>If you're a junior developer and you have a blog of your own where you write about technical content, <a href="https://twitter.com/hamatti">tweet a link to it to me</a>. I'll promise to read your posts.</p>
Building a website with a static site generator, part 2: Eleventy
2020-03-18T00:00:00Z
https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-2-eleventy/
<p>In this series:</p><ul><li>Part 1: <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/">Building a website with a static site generator, part 1: Setup</a></li><li>Part 2: Building a website with a static site generator, part 2: Eleventy</li><li>Part 3: <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-3-domain-analytics-and-forms/">Building a website with a static site generator, part 3: Domain, Analytics and Forms</a></li></ul><p>In <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/">the previous post in this series</a>, we created accounts, created new projects and set up a basic website with Eleventy, Netlify, Netlify CMS and GitHub. In this post, we're gonna take a deeper look into Eleventy which takes care of building the website by combining layout templates with blog post data from Markdown files.</p><p>First, let's start by making some changes. At the end of this post, you can find more information about the project structure and configuration if you want to dig a bit deeper into Eleventy.</p><h2 id="update-metadata-json">Update metadata.json</h2><p>If you run <code>npx eleventy --serve</code> and open <code>localhost:8080</code>, you see instructions to update <code>_data/metadata.json</code> so let's open that first.</p><pre><code class="language-json">{
"title": "Your Blog Name",
"url": "https://myurl.com/",
"description": "I am writing about my experiences as a naval navel-gazer.",
"feed": {
"subtitle": "I am writing about my experiences as a naval navel-gazer.",
"filename": "feed.xml",
"path": "/feed/feed.xml",
"id": "https://myurl.com/"
},
"author": {
"name": "Your Name Here",
"email": "youremailaddress@example.com"
}
}</code></pre><p>This file contains basic information that is used in different places in your website. It's a very handy place to store basic information about your site and yourself so that if for example your email address changes, you only need to change it in one place rather than trying to remember to change it everywhere.</p><h2 id="remove-instructions-block-from-your-layout">Remove instructions block from your layout</h2><p>Head over to <code>_includes/layouts/base.njk</code> and remove the <code>div</code> with class <code>warning</code> at lines 28–35.</p><h2 id="customize-layout">Customize layout</h2><p>The default starter pack looks simple and for your personal site, you probably want to build something custom. I am not much of a designer myself so I've been using <a href="https://html5up.net/">HTML5Up's layouts</a> for years now. They are HTML+CSS templates that are free to use under <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a> license (meaning you can use, modify and do whatever you want as long as you give credit to them).</p><h3 id="copy-css-javascript-over">Copy CSS & Javascript over</h3><p>Let's see how I would make <a href="https://html5up.net/read-only">Read Only</a> layout fit into this project. First, I download and unzip it into a temporary folder. It is a single-page layout but we can simply use the same looks for separate pages. Then, I'll copy everything from <code>assets/css/</code> to <code>/css</code> folder in our project and replace the stylesheet links in <code>base.njk</code> (change <code>index.css</code> -> <code>main.css</code> and add one for <code>fontawesome-all.min.css</code>. Second, I move the entire <code>/assets/js</code> folder to the root of our project. To make sure these JS files are accessible in our site, we also need to add a passthroughCopy setting in <code>.eleventy.js</code>:</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">eleventyConfig.addPassthroughCopy("js");</code></pre><!--kg-card-end: html--><p>The layouts from HTML5Up tend to have a lot of Javascript and eventually I end up removing it all but something might break if you just don't have them at all so I'll leave pruning the unneeded Javascript for homework if you wish to get rid of them. Add <code><script></code> tags into <code>base.njk</code> to load those Javascript files into your site.</p><h3 id="copy-modify-html">Copy & modify HTML</h3><p>Next, I often copy the body from the Read Only's <code>index.html</code> file into our <code>base.njk</code> and adjust individual HTML tags and classes to match the structure and nunjucks loops that we have in our page. This is one of the most tedious parts of taking these kind of existing layouts into Eleventy. But luckily we pretty much only have to do it once.</p><p>What I did at this point, was replacing the entire <code><body></code> tag of the original <code>base.njk</code> with the <code><body></code> of the Read Only template. Then I only left elements I want to have there every time: <code><header></code>, <code><nav></code> and the basic structure of <code><div id="wrapper"><div id="main"></div></div></code>.</p><p>I then replaced the <code><ul></code> in the <code><nav></code> with what we originally had to maintain our navigation.</p><p>Finally, I took the front page content (anything we replaced with <code></code> and took that into <code>index.njk</code>. Here I replaced the first section with short introduction on who I am and copied what we had there before into the second section:</p><pre><code class="language-html"><section id="two">
<div class="container">
<h3>Recent blog posts</h3>
<ol reversed="" class="postlist" style="counter-reset: start-from 4">
<li class="postlist-item">
<a href="https://hamatti.org/posts/my-most-used-bookmarklets/" class="postlist-link">
My most used bookmarklets
</a>
<time class="postlist-date" datetime="2024-03-18">18 Mar 2024</time>
<a href="https://hamatti.org/tags/browsers/" class="tag">browsers</a>
</li>
<li class="postlist-item">
<a href="https://hamatti.org/posts/external-syntax-error-13-playgrounds/" class="postlist-link">
Syntax Error #13: Playgrounds
</a>
<time class="postlist-date" datetime="2024-03-17">17 Mar 2024</time>
</li>
<li class="postlist-item">
<a href="https://hamatti.org/posts/on-content-creation-and-personal-web/" class="postlist-link">
On content creation and personal web
</a>
<time class="postlist-date" datetime="2024-03-16">16 Mar 2024</time>
<a href="https://hamatti.org/tags/blogging/" class="tag">blogging</a>
</li>
</ol>
<p>
More posts can be found in
<a href="/posts/">the archive</a>.
</p>
</div>
</section></code></pre><p>At this point, you also need to start thinking about what kind of structure and content you want to have in your page. Do you want to show your recent blog posts in the front page or dedicate the front page for telling about yourself and your skills?</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-07-at-6.48.58-PM.png" class="kg-image" /></figure><h2 id="individual-post-page">Individual post page</h2><p>The original post page only showed the title and the content but we might want to customize it a bit more. The way our Netlify CMS was set up, we are able to add a featured image that gets stored into a variable <code>thumbnail</code>. We can use that to show the picture in the beginning of the post.</p><p><code>_includes/layouts/post.njk</code></p><pre><code class="language-html"><section id="one">
<div class="image main" data-position="center">
<img src="" alt="" />
</div>
<div class="container">
<h2>Building a website with a static site generator, part 2: Eleventy</h2>
</div>
</section></code></pre><p>You can use similar approaches based on what fits your needs and which template you decide to use. If you want to build your own from scratch, there's less integration to do but requires deeper knowledge on frontend design and CSS.</p><h2 id="skills-section">Skills section</h2><p>Now we have a blog but as it's our portfolio page, maybe we also care about showcasing our skills. There are a couple of ways to do this.</p><h3 id="_includes-skills-njk">_includes/skills.njk</h3><p>First approach is to hard code all our skills into a snippet in a new file <code>_includes/skills.njk</code>. </p><p>To do this, I'll copy the third section from our Read Only template because it looks like a good layout for showcasing skills. It uses pictures that are not provided so I'll create a couple of decorative images that match my skills.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-07-at-6.49.33-PM.png" class="kg-image" /></figure><p>In this case, the <code>_includes/skills.njk</code> is just HTML listing out stuff. It can be a good approach if we only need to show those in one place and there's not many of them.</p><pre><code class="language-html"><section id="three">
<div class="container">
<h3>My skills</h3>
<p>
I'm a talented web developer and teacher.
</p>
<div class="features">
<article>
<a href="#" class="image"
><img src="images/web-development.png" alt=""
/></a>
<div class="inner">
<h4>Web Development</h4>
<p>
I can do frontend (HTML, CSS, Javascript) and backend (Python) development.
</p>
</div>
</article>
<article>
<a href="#" class="image"><img src="images/teacher.png" alt=""/></a>
<div class="inner">
<h4>Teaching</h4>
<p>
I wrote this tutorial, right?
</p>
</div>
</article>
<article>
<a href="#" class="image"><img src="images/fun.png" alt=""/></a>
<div class="inner">
<h4>Having fun</h4>
<p>
Oh boi!
</p>
</div>
</article>
</div>
</div>
</section></code></pre><p>Another approach is to build a new collection of skills and display them on the website:</p><p>Instead of creating <code>_includes/skills.njk</code>, create a new folder <code>skills/</code> in the root of project. For each skill, create a separate markdown file, like this:</p><p><code>skills/web-development.md</code></p><pre><code class="language-markdown">---
title: Web Development
image: web-development.png
description: I can do frontend (HTML, CSS, Javascript) and backend (Python) development.
tags: skills
---</code></pre><p>Repeat this process for all your skills. As we added the tag <code>skills</code>, Eleventy will automatically create a collection <code>collections.skills</code> that we can refer to.</p><p>Now, let's create the <code>_includes/skills.njk</code> but this time, instead of hard coding the content, let's build it from our collection.</p><pre><code class="language-html"><section id="three">
<div class="container">
<h3>My skills</h3>
<p>
I'm a talented web developer and teacher.
</p>
<div class="features">
</div>
</div>
</section></code></pre><p>This approach makes it much easier for us to manage the skills. To manage which ones we want to show, we can add another field into the front matter (things between - - and - -) named <code>public: true</code> and set that <code>false</code> to items we don't want to display. You can then add an if clause after the for loop to only display the ones that are public:</p><pre><code class="language-html"><section id="three">
<div class="container">
<h3>My skills</h3>
<p>
I'm a talented web developer and teacher.
</p>
<div class="features">
</div>
</div>
</section></code></pre><p>Regardless of which approach you took, we need to include this <code>skills.njk</code> somewhere for it to actually be visible. For example, you can add it to your front page with</p><!--kg-card-begin: html--><pre class="language-html"><code class="language-html">{% include 'skills.njk' %}</code></pre><!--kg-card-end: html--><p>You can replicate this process for other things too. Maybe you have some portfolio projects you want to showcase? Make a collection and build a page to display them nicely.</p><p>And since we have our CMS system set up, there's no need to keep writing this content inside your code editor. By modifying the <code>admin/config.yml</code>, we can create a new CMS form for our skills. To do that, add the following to the file:</p><pre><code class="language-yml">- name: "skills"
label: "Skills"
folder: "skills"
create: true
slug: ""
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Image", name: "image", widget: "image" }
- { label: "Description", name: "description", widget: "string" }
- { label: "tags", name: "tags", widget: "hidden", default: "skills" }
- { label: "Public", name: "public", widget: "boolean" }</code></pre><h2 id="updating-our-live-website">Updating our live website</h2><p>To update the website in Netlify, we need to add our changes to git and push them to GitHub. For brevity and since this is not a git tutorial, I've kept my commit messages very simple. To become better at writing commit messages, I recommend Chris Beams' article <a href="https://chris.beams.io/posts/git-commit/">How to Write a Git Commit Message</a>.</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">git add .
git commit -m "Integrate HTML5Up layout"
git push origin master</code></pre><!--kg-card-end: html--><p>It will take a moment for Netlify to pick up your changes and deploy them. Once it's done, you can see your changes in your site.</p><p>If you want to check them out in live environment before committing to them, instead of committing changes to <code>master</code> branch, you can create a separate branch, push that to GitHub and create a Pull Request. This will trigger Netlify to create a preview deployment like it did when we created a new post using the CMS.</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-07-at-8.17.42-PM.png" class="kg-image" /></figure><p>After it's been deployed, try adding a skill via CMS by navigating to <code>/admin</code> and logging in. You should now see a new navigation item in the main navigation called "Skills". Creating a new one will once again create a pull request that you can preview and merge in GitHub.</p><hr /><h2 id="netlify-in-more-detail-project-structure">Netlify in more detail: Project Structure</h2><p>If you open your project in a code editor (I use <a href="https://code.visualstudio.com/">Visual Studio Code</a>, a free and really versatile tool), you see that there are quite a lot of files already from the starter project.</p><h3 id="_includes">_includes</h3><p>First folder we're gonna look at is <code>_includes</code>. Inside here, you can store snippets and individual components as well as layouts (see below). One file this starter kit comes with is <code>postslist.njk</code> which is a nunjucks template for creating an ordered list of blog posts.</p><h3 id="_includes-layouts">_includes/layouts</h3><p>Inside <code>_includes/layouts</code> folder you find three files: <code>base.njk</code>, <code>home.njk</code> and <code>post.njk</code>. First one, <code>base.njk</code> is our wrapper layout. It contains the head element with CSS stylesheets, Javascript scripts and other information that we want to have access to on each site. <code>home.njk</code> then uses that layout to build our front page and <code>post.njk</code> does the same for individual blog posts.</p><h3 id="_data">_data</h3><p>Eleventy works with collections and there are couple of ways to define custom collections. One of them is to have a JSON or Javascript file inside <code>_data</code> folder. The name of the file will be the name of the collection (so creating a file <code>numbers.js</code> will give us access to a collection called numbers). Inside the file, you either need to store a valid JSON array or to export a function that returns an array of data. For an imaginary <code>numbers.js</code> collection, we could write:</p><p><code>numbers.json</code></p><pre><code class="language-javascript">[1,2,3,4,5,6,7,8,9,10]</code></pre><p>or we can have a function that returns random numbers from an API</p><p><code>numbers.js</code></p><pre><code class="language-javascript">const axios = require("axios");
module.exports = async function() {
const numbers = await axios
.get(
"https://www.random.org/integers/?num=10&min=1&max=6&col=1&base=10&format=plain&rnd=new"
)
.then(resp => {
return resp.data;
})
.then(numbers => {
return numbers
.split("\n")
.filter(num => num)
.map(num => parseInt(num));
});
return numbers;
};</code></pre><p>Both of these are then accessible in your templates with a reference <code>numbers</code>.</p><h3 id="_data-metadata-json">_data/metadata.json</h3><p>One existing collection in this project is <code>metadata</code>. You should take some time to change its contents to match your site. It's wired into different places into the website currently and you can use it to store basic information you don't want to hard code into your templates like your email address.</p><h3 id="about-">about/</h3><p>To create new pages, you have two options. You can either add a file into the root with the name you want the path to be (for example <code>me.njk</code> will then become <code>localhost:8080/me</code>) or have a folder like <code>about/</code> with an index file like <code>about/index.md</code> as we can see in our example project. You can use either Markdown files (<code>.md</code>) or any templating files (in our case with Nunjucks, <code>.njk</code>).</p><h3 id="img-">img/</h3><p>We store our image files in <code>img/</code> folder in the root. These get copied into the final site by our build process.</p><h3 id="css-">css/</h3><p>Same as images, we store our CSS in this folder and it gets copied over.</p><h3 id="posts-">posts/</h3><p>Here we save our blog posts. Anything stored in this folder will be available in <code>posts</code> collection. When we use NetlifyCMS, our new posts appear here as Markdown files.</p><h2 id="configuration">Configuration</h2><p>One of the key files in any Eleventy project is <code>.eleventy.js</code> that controls our configuration. We can install and activate plugins, define template filters, build new collections and define other things.</p><p>Below, I'll walk you through a couple of functions that I use most and you can read more from <a href="https://www.11ty.dev/docs/config/">Eleventy's Configuration documentation</a>.</p><h3 id="addplugin">addPlugin</h3><p>We can add new plugins with <code>eleventyConfig.addPlugin</code> function. Eleventy has a nice <a href="https://www.11ty.dev/docs/plugins/">collection of official and community plugins</a> that you can install based on your needs. I haven't used the plugins yet a lot but looking at the list of them right now, I should start using table of contents plugin for my guides.</p><h3 id="addfilter">addFilter</h3><p><a href="https://www.11ty.dev/docs/filters/">Filters</a> are Javascript functions that take an input, do something and gives output. I use them most for formatting data: for example, an API might return the date in a machine readable format and we want to display it in human readable format like in the first <code>addFilter</code> that already exists in this project:</p><pre><code class="language-javascript">eleventyConfig.addFilter("readableDate", dateObj => {
return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat(
"dd LLL yyyy"
);
});</code></pre><p>We can then use this filter in our templates with the pipe operator <code>|</code>:</p><!--kg-card-begin: html--><pre class="language-html"><code class="language-html"><time>Invalid DateTime</time></code></pre><!--kg-card-end: html--><p>Building custom filters is a good way to keep your templates simple and easy to read while shoving the implementation details inside your Javascript files.</p><h3 id="addcollection">addCollection</h3><p>Earlier I mentioned one way to create <a href="https://www.11ty.dev/docs/collections/">collections</a> is to use <code>_data</code> folder and JSON or Javascript files. Another way is to use <code>addCollection</code> function. The first parameter defines the name of the collection and second is a callback function that returns desired data.</p><pre><code class="language-javascript">eleventyConfig.addCollection("fruits", function() {
return ['apple', 'avocado', 'banana'];
})</code></pre><h2 id="next-steps">Next steps</h2><p>In this post, we learned how to integrate an existing HTML/CSS layout to our Eleventy page and how to build collections and display data. Now it's time for you to make a plan of what you want to showcase in your site and start writing content.</p><p>For some ideas, here are some of my favorite websites of developers:</p><ul><li>Sarah Drasner: <a href="https://sarah.dev/">https://sarah.dev/</a></li><li>James Stone: <a href="https://www.jamesstone.com/">https://www.jamesstone.com/</a></li><li>Sara Soueidan: <a href="https://www.sarasoueidan.com/">https://www.sarasoueidan.com/</a></li><li>Fotis Papadogeorgopoulos: <a href="https://fotis.xyz/">https://fotis.xyz/</a></li><li>Wes Bos: <a href="https://wesbos.com/">https://wesbos.com/</a></li><li>Matthew Williams: <a href="http://findmatthew.com/">http://findmatthew.com/</a></li></ul><p>and you can also take a look around at my website <a href="http://hamatti.org/">http://hamatti.org/</a>.</p><p>In the last part, we'll look into how to buy a domain for your website from Netlify so you can direct your friends, family and potential future employers to a nice URL that tells them it's your home in the Internet.</p>
Building a website with a static site generator, part 1: Setup
2020-03-11T00:00:00Z
https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-1/
<p>I like a lot of things, and static site generators are on that list. This blog series is aimed for students and junior developers who want to build a personal website and a blog either as a portfolio, a journal or just a digital home in the highway of the information.</p><p>My stack of choice for this one is <a href="http://11ty.dev/">Eleventy</a> for the static site generator, <a href="https://www.netlifycms.org/">Netlify CMS </a>for content management, <a href="https://github.com/">GitHub</a> for hosting your code and <a href="https://www.netlify.com/">Netlify</a> for hosting the website. All of these tools are free to use. We'll build a site that you can update from a web UI and that gets deployed automatically to Netlify whenever you make a change.</p><p>For this tutorial series, I expect you to know the basics of HTML, CSS and Javascript. You don't have to be an expert though. Building your own website is a great way to improve all of those skills step by step. I've been doing that for 20 years and every new website project is a platform to educate myself with new things.</p><p>In this series:</p><ul><li>Part 1: Building a website with a static site generator, part 1: Setup</li><li>Part 2: <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-2-eleventy/">Building a website with a static site generator, part 2: Eleventy</a></li><li>Part 3: <a href="https://hamatti.org/posts/building-a-website-with-a-static-site-generator-part-3-domain-analytics-and-forms/">Building a website with a static site generator, part 3: Domain, Analytics and Forms</a></li></ul><h2 id="let-s-create-accounts">Let's create accounts</h2><p>If you already have accounts to GitHub and Netlify, you can skip this section.</p><h3 id="github">GitHub</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-06-at-11.16.29-PM.png" class="kg-image" /></figure><p>GitHub is a development platform that allows you to store your software projects through version control. In our case, we will store our website code inside it and automatically build the website into Netlify when it gets changed.</p><p>I won't walk you through how to use <a href="https://git-scm.com/">git</a> in this series so if you're not familiar with it, check out <a href="https://try.github.io/">GitHub's tutorials</a>.</p><p>Head over to <a href="https://github.com/join">https://github.com/join</a> and create an account.</p><h3 id="netlify">Netlify</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-06-at-11.19.13-PM.png" class="kg-image" /></figure><p>Once you have your Github account, it's time to create an account in Netlify. Head over to <a href="https://app.netlify.com/signup">https://app.netlify.com/signup</a> and sign up with your freshly created GitHub account.</p><h2 id="let-s-create-repository-netlify-site">Let's create repository & Netlify site</h2><h3 id="new-github-repository">New GitHub repository</h3><p>After you have a GitHub account, create a new repository for the website project. You can do that by going to <a href="https://github.com/new">https://github.com/new</a>, giving your project a name (I chose <code>eleventy-tutorial</code> but you can call it <code>my-website</code> or something that helps you know what it is).</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-06-at-11.35.50-PM.png" class="kg-image" /></figure><h3 id="new-netlify-site">New Netlify site</h3><p>We want to create a new site from git so go to <a href="https://app.netlify.com/start">https://app.netlify.com/start</a> and select GitHub as your source. Follow the process of authorizing Netlify on GitHub and select your repository. Make sure your build command is <code>eleventy</code> and your branch is <code>master</code>. </p><h2 id="let-s-install-tools-copy-a-starter-project">Let's install tools & copy a starter project</h2><p>On your development computer, open your terminal and navigate to wherever you store your software projects. I have a <code>~/code</code> folder in my macOS that is the home for all my code projects.</p><p>To run Eleventy and Netlify command line tools, we're gonna need <a href="https://nodejs.org/en/">Node</a> and <a href="https://www.npmjs.com/">npm</a>.</p><p>Check if you already have them installed by running</p><!--kg-card-begin: html--><pre class="language-bash"><code class="language-bash">node -v
npm -v</code></pre><!--kg-card-end: html--><p>If you don't have Node installed, I recommend using <a href="https://github.com/nvm-sh/nvm">nvm</a> to manager your Node versions. Follow their installation instructions and install Node. Once you have Node installed, it will come with npm so you're gonna be all good. We will also use <a href="https://nodejs.dev/the-npx-nodejs-package-runner">npx</a> to run commands without installing packages and that's included too.</p><p>To get started, we're gonna copy an Eleventy starter project from Eleventy's GitHub: <a href="https://github.com/11ty/eleventy-base-blog">https://github.com/11ty/eleventy-base-blog</a>.</p><p>Run <code>git clone git@github.com:11ty/eleventy-base-blog.git my-website</code> and git will download the starter kit for you. Navigate to this new folder with <code>cd my-website</code>.</p><p>Follow the instructions on that repository's readme file to install packages and once you're done, run <code>npx eleventy --serve</code> to start a development server. If everything goes smoothly, you should be able to open <a href="http://localhost:8080/">http://localhost:8080</a> and see a website like this:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-06-at-11.33.28-PM.png" class="kg-image" /></figure><h3 id="connecting-to-your-github-repository">Connecting to your GitHub repository</h3><p>Now go to your new GitHub repository we created earlier and find your GitHub project URL that looks like this: <code><a>git@github.com</a>:Hamatti/eleventy-tutorial.git</code> if you're using ssh or <a href="https://github.com/Hamatti/eleventy-tutorial.git"><code>https://github.com/Hamatti/eleventy-tutorial.git</code></a> if you're using https.</p><p>Let's remove the connection to the starter kit repository by running <code>git remote remove origin</code> and replace it with our by running <code>git remote add origin [your_url]</code> where you replace <code>[your_url]</code> with the one you copied in the previous paragraph.</p><p>Now run <code>git push origin master</code> to get your new project into your GitHub repository.</p><h3 id="netlify-cms">Netlify CMS</h3><p>Now that we have a working Eleventy project set up, let's integrate Netlify CMS so we can write posts using a Web UI through our website.</p><p>First, create a new folder at the root of your project called <code>admin/</code>. Inside it, we need two files: <code>index.html</code> and <code>config.yml</code>.</p><p><code>admin/index.html</code></p><pre><code class="language-html"><!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Manager</title>
</head>
<body>
<!-- Include the script that builds the page and powers Netlify CMS -->
<script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
</body>
</html></code></pre><p><code>admin/config.yml</code></p><pre><code class="language-yml">backend:
name: git-gateway
branch: master # Branch to update (optional; defaults to master)
publish_mode: editorial_workflow
media_folder: "images/uploads"
collections:
‐ name: "posts" # Used in routes, e.g., /admin/collections/blog
label: "Posts" # Used in the UI
folder: "posts" # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
slug: "---" # Filename template, e.g., YYYY-MM-DD-title.md
fields: # The fields for each document, usually in front matter
‐ { label: "Layout", name: "layout", widget: "hidden", default: "post" }
‐ { label: "Title", name: "title", widget: "string" }
‐ { label: "Publish Date", name: "date", widget: "datetime" }
‐ { label: "Featured Image", name: "thumbnail", widget: "image" }
‐ { label: "Body", name: "body", widget: "markdown" }</code></pre><p>There are many options and ways to configure your NetlifyCMS. This one adds one collection called Posts with fields for title, publish date, featured image and body. Once you figure out what you want, you can adjust these. Find out more options in <a href="https://www.netlifycms.org/docs/configuration-options/">Configuration documentation page</a>.</p><p>Finally, we need to add a single line in our <code>.eleventy.js</code> file.</p><p>Find the lines</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">eleventyConfig.addPassthroughCopy("img");
eleventyConfig.addPassthroughCopy("css");</code></pre><!--kg-card-end: html--><p>and add a line</p><!--kg-card-begin: html--><pre class="language-javascript"><code class="language-javascript">eleventyConfig.addPassthroughCopy("admin");</code></pre><!--kg-card-end: html--><p>after them. Now if run <code>npx eleventy --serve</code> and navigate to <a href="http://localhost:8080/admin/">http://localhost:8080/admin/</a>, you should see a login page:</p><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/03/Screen-Shot-2020-03-07-at-12.02.38-AM.png" class="kg-image" /></figure><h3 id="netlify-identity">Netlify Identity</h3><p>We don't want everyone to be able to login to our CMS and make changes so we need to add authentication. Netlify has built-in auth and we're gonna use it.</p><h4 id="code-changes">Code changes</h4><p>First, we need to add a couple of Javascript scripts to our layouts so that we can authenticate:</p><p>In <code>_includes/layouts/base.njk</code>,</p><ol><li>add <code><script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script></code> inside your <code><head></code> tag</li><li>and the following script right before <code></body></code>:</li></ol><pre><code class="language-html"><script>
if (window.netlifyIdentity) {
window.netlifyIdentity.on("init", user => {
if (!user) {
window.netlifyIdentity.on("login", () => {
document.location.href = "/admin/";
});
}
});
}
</script></code></pre><p>In <code>admin/index.html</code>, you need to</p><ol><li>add <code><script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></code> inside the <code><head></code> tag.</li></ol><h4 id="enabling-netlify-identity">Enabling Netlify Identity</h4><p>Go to Identity tab in Netlify and click Enable button. </p><p>In the Identity settings, go to <code>Services->Git Gateway</code> and enable that (you need to auth Netlify again with GitHub) and you're good to continue.</p><p>Add your changes to git with <code>git add . && git commit -m "Add NetlifyCMS"</code> and push it to GitHub <code>git push origin master</code>.</p><p>Once the newest changes have been deployed (you can always check that from Deploys tab), go back to Netlify Identity page and invite yourself as a user. Then go to your email, accept the invite and you should be greeted with a form to set up your password. After this, you'll be redirected to the admin page.</p><h2 id="our-first-blog-post">Our first blog post</h2><p>Now create a new blog post with some content and click save. Two things will happen:</p><p>(1) It will create a new pull request to your GitHub repository and (2) this will trigger a preview build in Netlify. In Github, you can see links and the last one (<strong>netlify/[yoursitename]/deploy-preview </strong>— Deploy preview ready!) will take you to see the changes before you deploy them. If everything looks good, go ahead and click Merge Pull Request. This will in turn trigger the main build in Netlify and once it's done (you can follow it from Deploys page in Netlify), your new post is live!</p><p>With today's work, we have set up a GitHub repository, Netlify project, created a new Eleventy site, integrated NetlifyCMS and deployed all of this. You can now start blogging.</p><h2 id="next-steps">Next steps</h2><p>The best way to learn is to tinker with something you have. So now that we have set up the basics, take some time to write posts, experiment with different formatting options in the CMS and maybe give it a try to add another collection to your <code>admin/config.yml</code> by following <a href="https://www.netlifycms.org/docs/configuration-options/">the docs</a>.</p><p>In the next post, we're gonna learn how to customize Eleventy so we can decide what things look like and how we can customize things.</p><hr /><p>If you liked this post, why not check out <a href="https://hamatti.org/blog">other posts in my blog</a>, <a href="https://hamatti.org/guides/humane-guide-to-debugging/">my Debugging Web Apps guide</a> or <a href="https://twitter.com/hamatti">follow me on Twitter</a>. See you next time!</p>
How my site is built with Eleventy + Ghost
2020-03-06T00:00:00Z
https://hamatti.org/posts/how-my-site-is-built-with-eleventy-ghost/
<h2 id="first-iteration-2019">First iteration, 2019</h2><p>In the beginning of 2019, I started building this website and decided to use <a href="https://www.11ty.dev/">Eleventy</a> as my static site generator. I got the template from <a href="http://html5up.net/">HTML5Up</a> (love it) and started making things happen.</p><p>I chose Eleventy mostly because a friend was using it as well and following him work on his site got me curious. I haven't really taken full advantage of its features yet but one day I'll dive deeper into components and such. The ability to write my own collection functions, template filters and helper functions in Javascript is a really big plus for me.</p><p>Originally, this blog was a collection of posts written in Markdown but writing posts in Markdown became bit of a burden and I ended up not writing anything. I missed a good editor experience. Maybe it's because I write code in my editor and writing blog posts requires a very different thinking process or maybe it was just a good excuse I came up with for not writing more.</p><h2 id="the-improvement">The improvement</h2><p>(I did <a href="https://www.youtube.com/watch?v=I4-5zTRJ8x8">a talk in HelsinkiJS</a> about this process in January)</p><p>Last December, I was listening to <a href="https://www.indiehackers.com/podcast/007-john-onolan-of-ghost">the Indie Hackers Podcast episode where Ghost founder John O'Nolan was discussing Ghost</a>. I had looked at Ghost earlier some years ago but never ended up using it. Now the time was right, and I like the not-for-profit model and open source approach so I decided to investigate how to integrate Ghost with my existing Eleventy site.</p><p>I found a blog post <a href="https://david.darn.es/tutorial/2019/06/01/use-eleventy-to-generate-a-ghost-blog/">Use Eleventy To Generate A Ghost Blog by David Darnes</a> which gave me hope that it wouldn't take me months to build. I installed Ghost on my local dev machine and started building the integration.</p><p>Thanks to the Ghost ContentAPI, getting data from my Ghost instance was easy but other things turned out to be a bit of a challenge. First of all, I had old posts written in Markdown and new posts written in Ghost and combining those two wasn't easy. They had different data models and I was running a bit in circles trying to match them.</p><p>Second, creating posts in localhost meant that all the pictures were also hosted on that instance. So I built a function as part of my build process that downloaded all the pictures from posts and fixed the references. Somehow, it ended up also messy because Eleventy kept re-building every time it downloaded a picture and ended up in an endless loop in development mode.</p><p>Last, hosting the Ghost in my localhost and bundling fetching the posts as part of the build process turned out to be very problematic. It meant that doing any changes into the site or copy outside the blog required fetching all posts and I could only do that with one computer.</p><h2 id="second-iteration-2020">Second iteration, 2020</h2><p>So last week I decided to redo it. Here's where we are now:</p><p>I moved Ghost to a Digital Ocean VPS so I can edit anywhere with any computer. I decoupled post fetching from the build process into a separate Node script. Everytime I want to get posts, I run <code>npm run fetch</code> which turns Ghost posts into Markdown files and stores them alongside my older Markdown posts. I'm not 100% sure if that's the best idea but it solved another of my problems: different data models. And I can now edit the copy or layout without having to fetch all the posts on every save.</p><p>By saving the Ghost posts as a Markdown files, I can also keep the files in my version control and everything doesn't horribly break if something happens to my Ghost instance.</p><p>All together, the new version feels much more robust. I'm still not sure if saving Ghost posts as Markdown files is the best approach but it's a big improvement.</p><h2 id="redirects">Redirects</h2><p>Oh, one more thing. I built a redirect layout template which allows me to do easy redirects like <a href="https://hamatti.org/youtube">https://hamatti.org/youtube</a> and have them do a 301 redirect to wherever I want. It's not exactly a short url service but it serves a similar function.</p><pre><code class="language-html"><!doctype html>
<html>
<head>
<meta http-equiv="Refresh" content="0; url="/>
</head>
</html></code></code></pre>
New Guide - Humane Guide to Debugging Web Apps
2020-03-04T00:00:00Z
https://hamatti.org/posts/new-guide-humane-guide-to-debugging-web-apps/
<p>If there's one skill I would tell students and junior developers to really focus on, it's debugging. The ability to debug your programs, figure out why things are not working and how to find the right spots were they go wrong is an ageless skill. It's not gonna get old and it's not tied to any specific technology.</p><p>To help developers become better at debugging, I wrote <strong><a href="https://hamatti.org/guides/humane-guide-to-debugging/">Humane Guide to Debugging Web Apps</a>. </strong>It walks the reader through the fundamentals of the mindset and process, provides practical tools and tips for debugging Javascript and CSS code and finally talks about some of my favorite non-technical approaches.</p><p>Let me know what you think of the guide by tweeting at me at <a href="https://twitter.com/hamatti">https://twitter.com/hamatti</a>.</p>
Minimal Travel Table Top Game Collection
2020-02-03T00:00:00Z
https://hamatti.org/posts/minimal-travel-tabletop-game-collection/
<p>I <strong>love</strong> table top games. Whether it's a complex big box strategy game like Eclipse or Power Grid, a more casual light-weight game like Ticket to Ride or Carcassonne or a smaller game like Fluxx, I'm excited to play it.</p><p>But apparently the industry is still being dominated by traditional retail market which leads to games being sold in huge boxes, even when the actual contents of the game can fit your pocket. As someone who travels a lot and in general, likes to find ways to fit everything I own into a backpack, I've been interested in different micro games and movements around that. BoardGameGeek is a nice resource for me. There are plenty of designers who have built <a href="https://boardgamegeek.com/thread/2339779/2020-9-card-game-print-and-play-design-contest">PnP (<em>Print and play) </em>games</a> that are basically just rule sets and designs that you can print yourself rather than buying a produced version from a store. There's also a community in BGG for games in mint tins where the entire contents of the game must fit into a mint tin container.</p><p>During Christmas, these ideas got combined and mixed up in my head and as I was playing around with an old deck box that used to contain my Pokemon TCG cards, I got an idea. I started to research games that require small amount of cards. After a bit of back and forth and doing inventory on my board game stuff, I ended up with a following set.</p><p>I wanted a collection that doesn't take much space and contains easy and fast games that still have certain amount of depth in strategy. These games are meant to be played when waiting for more players to join (most are 2-player games) or while traveling on the train or during a break at a conference.</p><p><em>Disclaimer: These cards are custom made for personal use only. I have reused some content from the Internet and some redesigns of the actual cards. I do not own the rights for those art works and thus I will not provide the files for anyone else to print these nor will I sell any of these cards.</em></p><h2 id="the-contents">The Contents</h2><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/IMG_20200203_165118__01-1-1.jpg" class="kg-image" /></figure><p>Let's start with the basic pieces. I use <a href="https://ultimateguard.com/en/deck-case/deck-case-80">an Ultimate Guard 80+ deck case</a>. Inside that, I have a case from <a href="https://varianto25.com/collections/playing-cards/products/code-deck-classic-playing-cards">Varianto25's code:deck</a>. I took out the cards and it functions now as a case for all the tokens and dice. For tokens, I have 10 plain grey discs, 4 Carcassonne meeples, 3 sets of small dice in different colors (these are from Pokemon TCG Elite Trainer Boxes) and two larger dice in different colors.</p><p>The token selection is mostly based on a guess of what I'd like to have to build a versatile box. Not all of them are used in the games included in this box but for example with the 18 small dice, you can play many different dice games. I have also repurposed them to work as counters (we use them for damage counters in Pokemon TCG which gave me the idea to use them as keeping score or resources).</p><p>For sleeves, I use <a href="https://www.dragonshield.com/product/red-matte-100-standard">Dragon Shield's Red Matte sleeves</a>. Sleeves serve double purpose: they protect the cards and make it possible to have double-sided cards.</p><p>I put tokens into the code:deck box and put that inside the UG deck case. Let's take a look at the cards & games included in this collection.</p><h2 id="the-games">The Games</h2><p>There are 6 games included in this set. I spent countless of hours over my holiday in Affinity Designer and DeviantArt to make good looking cards. I calculated that the box fits 50 sleeved standard sized cards with the token box. I decided to print cards as double-sided so that I could switch a game to another by flipping the cards inside sleeves.</p><h3 id="pico">Pico</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/pico.png" class="kg-image" /></figure><p>First game in this collection is <a href="https://boardgamegeek.com/boardgame/2051/pico">a classic trick-taking game Pico</a>. It is a two player game that is played with 11 cards with numbers from 2 to 10 + 13 and 16 with red dots marking score from 1 to 4.</p><p>You shuffle up the cards, deal 5 to each player and hide the 11th. First round, you play one card at the time face-down and reveal at the same time. The larger card wins <em>unless</em> it's over 2x bigger. So 10 wins 5 but loses to 4. Winning card stays in front of the winning player while the losing card returns to loser's hand. This is repeated until one player has only one card left. At that point, total score is calculated and second round begins. Players swap their piles of cards and repeat with the same rules. After these two rounds, winner is the player with most points.</p><p>Pico is a simple, easy to learn, includes mind games (which I love) and runs fast.</p><h3 id="win-lose-banana">Win, Lose, Banana</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/wlb.png" class="kg-image" /></figure><p>If Pico is simple and easy to play, <a href="https://boardgamegeek.com/boardgame/47082/win-lose-or-banana">Win, Lose, Banana</a> is even simpler but deceivingly challenging. It's a 3-player game that has only 3 cards. You shuffle up the cards and deal one to each player.</p><p>The winner reveals their card. Now it's their job to guess which player is the banana. If they guess correctly, both they and the banana player get a point. If they guess incorrectly, the loser gains a point. The beauty of the game comes from bluffing and bargaining that the loser and banana player can do to convince the winner to pick them.</p><h3 id="famous-first-downs">Famous First Downs</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/ffd.png" class="kg-image" /></figure><p>The world's smallest football game, <a href="https://boardgamegeek.com/boardgame/124760/famous-first-downs-worlds-smallest-football-game">Famous First Downs</a> is already a great travel game. The official product comes with 9 cards and you use coins to keep track of different things.</p><p>The problem with my case was that the game uses double-sided cards within the game (to save space, love 'em for that) but I need every game to be on the same side of the cards. So I took the original cards, created simplified designs and solved the problem that way.</p><p>This 2-player sports strategy game has the best aspect of football - trying to outplay your opponent with your plays - without the worst part - actually running on the field. Just like real football, this game looks simple but the strategies go deep. In addition to choosing individual plays to gather yards in an attempt to score a touchdown or a field goal, you can gain and spend momentum tokens to boost your success or hinder your opponent's attempts.</p><p>In the game, you play a game of football where you alternate between playing offense (red cards) and defense (blue cards). Offense chooses a play and puts that face-up into the field. The defense then chooses their play in secret and plays it face-down. After this, the offense chooses one of the 3 available throw or run plays (marked red in top of the card). Then you look what the corresponding defensive play is and either gain or lose yards.</p><p>I replaced the coins requirement by adding the 20 dice into the mix as they can be used to keep track of both the yards gained as well as the momentum tokens. </p><p>If you are even little bit into sports and table top games, I highly recommend checking out the original Famous First Downs. They also have other sport games in their series of small card games. I haven't played any others but this one is often cited as the best in the series.</p><h3 id="during-spring-summer-and-winter">During Spring, Summer and Winter</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/seasons.png" class="kg-image" /></figure><p><a href="https://boardgamegeek.com/boardgame/159359/during-spring-summer-and-winter">This game (also known as 春夏冬中 or Akinai-chu)</a> is a beautiful game for 3-4 players. There are 18 cards (17 in the picture + 1 blank card): 4 seasons with values 1-1-2-3, a rainy season card and a blank card. The game is based on hidden information and asking questions that offer you bits and pieces that you need to put together.</p><p>In the beginning of the game, 1 or 2 cards is put face down on the table and rest shuffled and dealt to players. Each turn, a player has two possible actions: <em>a question and a challenge. </em>You can ask any other player either <em>the total value of a season (for example, Spring) </em>or <em>total number of cards with a given value (like number 2). </em>If a player asks for the value of a season, they must reveal one card from their hand and place that face-up in front of them.</p><p>Once you figure out what the hidden card(s) is/are, you can make a challenge and guess the season and value. They secretly then look at the card(s) and if they are correct, score the point written on the card. 6 points wins the game.</p><p>There are couple of extra rules that make the guessing bit more challenging but I won't go into the rules in-depth here.</p><p>If you have ever played <a href="https://boardgamegeek.com/boardgame/98778/hanabi">Hanabi</a> and enjoyed that, give this game a try as well!</p><h3 id="zeppelin-derby">Zeppelin Derby</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/zeppelin.png" class="kg-image" /></figure><p>Fifth game in this collection is <a href="https://boardgamegeek.com/boardgame/145277/zeppelin-derby">Zeppelin Derby</a>, a 2-player race game. This game is one of the simpler side in this collection. You have a race track (3 light blue cards on the left) with two zeppelins racing against each other. You roll a dice, move your zeppelin and use the other cards in the picture to gain advantage or to harm your opponent's progress.</p><p>First one to finish line wins.</p><h3 id="hanamikoji">Hanamikoji</h3><figure class="kg-card kg-image-card"><img src="https://hamatti.org/assets/img/ghost//2020/02/hanamikoji.png" class="kg-image" /></figure><p>I wanted to add one bit more complex game into the mix, just to see what can be done with limited space. I chose <a href="https://boardgamegeek.com/boardgame/158600/hanamikoji">Hanamikoji</a> which turned out to work really well in this collection. It is a 2-player area control game. It's also the one that has been downsized the most out of this games. The original game comes in a box, the geisha cards (which I replaced with my favorite video game characters) are big and the tokens (cards in the bottom) are square tokens. I made everything into a standard size card and redesigned the theme to be video games rather than geishas.</p><p>The upper row of cards in the picture are characters who we try to win to our side. The number in the corner tells us how many items there are for that character as well as how many points this character is worth. Second row is items that are played to control a certain character. Bottom row is action tokens.</p><p>All items are shuffled and dealt to players. Each round, player has four actions they must take but they can decide the order. #1 lets you hide one card under it - this will be revealed at the end of the round and counted for you. #2 forces you to discard two cards in hidden - they won't score any points for you. #3 makes you to present 3 cards to your opponent. They then pick one and play it on the field and you play the other two. For #4, you choose four cards and make them into two pairs. Your opponent then chooses one pair and plays them and you play the rest.</p><p>Each card is played to its corresponding character as noted by the colors and the themes (Thunder Stone for Pikachu, Tomato for Kirby, Arwing for Fox, Master Sword for Link, Super Crown for Bowser, Bell for Goose and Strawberry for Madeline). At the end of the round, if your side has more cards for a specific character than your opponent, you move a token to your side of the card to note that you have won them over. If it's a tie, the token does not move.</p><p>If after any rounds, one player has either 11 points or controls 4 characters, game ends and that player wins. </p><p>Hanamikoji is one of my most recent favorite games. The way cards are played with the actions really forces you to make non-optimal moves and forces you to balance many things in your mind at the same time.</p><h2 id="conclusion">Conclusion</h2><p>All in all, I'm happy about how this collection turned out. It's the first version as I had to pick some games and see how it goes. I'll probably do some changes into it as I discover new games or get bored to the current ones.</p><p>I used <a href="https://www.makeplayingcards.com/">MakePlayingCards.com</a> to print the cards and this entire collection of cards cost me less than 20 euros (including shipping to Finland). I do recommend buying the original games if you are not restricted by the space available.</p><p>I had a lot of fun making the cards and trying to find ways to put multiple interesting games into such small space. I can't wait to take this on the road with me the next time I travel to test it out in the real world. </p>
Helsinki Dev Lunch
2020-01-16T00:00:00Z
https://hamatti.org/posts/helsinki-dev-lunch/
<p>
<em>My first touchpoint to developer lunches was when I was working in San
Francisco. At Chartio, we'd host Lunch & Learn sessions within our
developer team and every session we'd eat lunch together and someone would
introduce others into different topics.</em>
</p>
<p>
When I moved back to Finland, and especially after returning to my hometown of
Turku, I wanted to do something similar. I didn't have a team of developers so
I figured I'd invite others for a once-a-month developer lunch. Before even
the first lunch happened, the idea had evolved into
<a href="http://turkufrontend.fi/">Turku <3 Frontend </a>developer community
and the lunch idea was set aside.
</p>
<p>
Last summer, it resurfaced as I was thinking about new ways to find a more
casual way to get people together. On our first Helsinki Dev Lunch, we had
four people - all friends of mine from other communities - and throughout the
fall, we'd grow into a regular monthly lunch group with 8-10 people each time.
</p>
<p>
So what is <strong>Helsinki Dev Lunch</strong>? It's a monthly casual
gathering of people interested in software development. We welcome students,
hobbyists, professionals and who ever is interested to join. There are no
corporate sponsorships, no talks, no agenda. Just a bunch of great people who
work in different places coming together like a team lunch.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/02/hki-dev-lunch-1.jpeg" class="kg-image" />
</figure>
<p>
While the first people in the community were my close friends, we quickly grew
outside just my networks. People started to join from different places:
meetups, social media, hearing from friends. It was much more than I hoped
for. An opportunity for all participants to meet new people and to discuss -
well, anything really.
</p>
<p>
We've talked about some developer stuff like Rust and PHP or differences
between working in consultancy and product houses but also non-developer stuff
like scifi, traveling, family life and pets. The stuff that you don't so often
end up talking about when meeting new people in meetups.
</p>
<p>
From the very get-go, Helsinki Dev Lunch is built with lowest possible
thresholds of participation. We welcome everyone, we don't have registration
(just show up and say hi, also no bad feelings if you have to cancel) and you
don't need to know anything in particular to be able to enjoy discussions.
</p>
<h2 id="how-to-do-your-city-dev-lunch">How to do [your city] Dev Lunch?</h2>
<p>
Some people have wished that their city would also have a Dev Lunch concept.
I'm gonna share you how it's done so you can start your own.
</p>
<p><strong>Choose a restaurant and date</strong></p>
<p>
My recommendation is selecting a lunch place that offers buffet for two
reasons: 1) less waiting for getting food, especially if the group grows and
2) most often there's something for everyone.
</p>
<p><strong>Invite people</strong></p>
<p>
If you already work with developers, you can start with your own team and ask
everyone to bring one friend. If you already have connections to people in
different companies, invite them. If you don't know people but want to get
started, look for local communities: are there any
<a href="https://meetup.com/">meetups</a> in your area? Talk with the
organizers and ask if you could share the invitation with people in that
community. Or if there are Slack communities or Facebook groups for
developers, invite people from those.
</p>
<p>
I started by posting about this to Twitter and LinkedIn. And for the first 6
months, that was all I did. Sometimes someone would come to talk to me in a
meetup and ask if I'm "the Lunch Guy". This week I built a website.
</p>
<p><strong>Enjoy your food</strong></p>
<p>
Last step is to enjoy food, be welcoming to people joining, help them meet the
people you already know and have fun.
</p>
Year in review 2019
2019-12-31T00:00:00Z
https://hamatti.org/posts/year-in-review-2019/
<p>
<em>Another year is coming to its end. Last few years I have done some
reflection on the past year, mainly to document to myself what I have been
working on, how I feel about the year and to fight my impostor syndrome by
writing down and quantifying my accomplishments.</em>
</p>
<p>
To celebrate the end of 2018 and the beginning of 2019, we got together with a
couple of friends and hoisting the champagne glasses to the pitch black sky, I
told my friends I had 3 major goals for 2019: (1) speaking in an international
tech conference, (2) becoming a full-time developer advocate, and (3) moving
abroad.
</p>
<p>So I guess, it's good to start by reflecting on these three goals first.</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/02/001-pyconcz-fridayhug.jpg" class="kg-image" />
</figure>
<h2 id="-1-talks-workshops-and-events">(1) Talks, workshops and events</h2>
<p>
With good progress that I made in 2018, I started 2019 by submitting a lot of
proposals to a lot of conferences. Many of the mornings this year I woke up to
see rejection emails from countless conferences. But eventually, I got a
couple of acceptance emails as well and in addition to conferences, was able
to improve my speaking gigs all around in local events as well.
</p>
<p>Out of my 53 talks or workshops, here are my highlights of 2019:</p>
<ol>
<li>
<a href="https://hamatti.org/talks/i-teach-therefore-i-learn/">I teach, therefore I learn at PyCon CZ</a>
</li>
</ol>
<p>
My first big talk of the year was in the big picture my favorite moment of the
year. I traveled around Europe for 3 weeks, gave this talk in PyCon CZ,
another talk in the lightning talks sections, met really nice people from all
around the world and on my way home, gave two talks in meetups in Berlin as
well.
</p>
<p>
2.
<a href="https://hamatti.org/talks/contemporary-documentation/">Contemporary Documentation at PyCon Estonia</a>
</p>
<p>
While the first talk was a great conference & travel experience, I really
liked the Contemporary Documentation talk because of its side effects. I gave
the talk in couple of meetups in Finland, talked about the topic in
<a href="http://webbidevaus.fi/56">Webbidevaus podcast</a>, and I got to have
a lot of great discussions about documentation practices with many developers.
And in the conference itself, I made a couple of good friends and had
wonderful time in Tallinn.
</p>
<p>
3.
<a href="https://www.youtube.com/watch?v=o8Un1w30IDk">Self-Documenting Code at PyCon Sweden</a>
</p>
<p>
I love lightning talks as a concept. In my first conference in PyCon Finland
back in 2016 I didn't quite understand them but this year they became my
favorite portion of any conference. Five minutes is a challenging time frame
that creates both great talks and a great opportunity to share community
experiences and showcase projects.
</p>
<p>
In PyCon Sweden, I gave a 4-minute talk about a side project that I had
created to showcase how silly and harmful the trope of self-documenting code
is. It was a lovely experience and I really enjoyed how my jokes hit home with
the crowd. And in addition to the talk, the trip was a great one and I loved
getting to know wonderful people from the Stockholm community.
</p>
<p>
In addition to these three, I finished the year with 53 talks, workshops,
podcasts, Twitch streams and other public speaking engagements. It was the
first year I got to be on a podcast (4 in total: Identio Prochat,
Bisnesvallankumous (twice!) and Webbidevaus). I really enjoyed being on a
podcast and hope to have more opportunities for that in 2020.
</p>
<p>
In addition to talking about tech, I also talked/coached in a couple of
startup accelerators. I did workshops on MVP and prototyping for Cambridge
Venture Camp, Hatch Incubator, *ship startup festival and helped teams with
pitching at Startup Journey. I also talked to students about dreaming and
reaching your goals in multiple occasions.
</p>
<figure class="kg-card kg-image-card kg-width-wide">
<img src="https://hamatti.org/assets/img/ghost//2020/02/avodaco-2.jpg" class="kg-image" />
</figure>
<h2 id="-2-developer-advocacy">(2) Developer Advocacy</h2>
<p>
As I had been running
<a href="https://hamatti.org/p/be2eb409-0b74-4f56-9cc2-58c7f9191e74/www.turkufrontend.fi">Turku <3 Frontend</a>
community for a few years, I started to get more and more interested in
building developer communities more seriously. After lots of discussions,
planning and negotiating at work, I was finally able to transition into a
full-time role as a developer advocate - or developers' best friend like I
tend to call it - at Futurice.
</p>
<p>
Since summer, I have been really happy at working on all sorts of stuff:
meetups, hackathons, content marketing, speaking in events (see (1) above),
encouraging and sparring my colleagues to speak in events and so on. And I'm
so glad that with a great team of marketing and recruitment people, I got a
lot of responsibility to help work on our recruitment funnel on a more
tactical level too.
</p>
<p>
In August I started my first developer newsletter,
<a href="https://hello.futurice.com/dev-breakfast">Dev Breakfast</a> and
curating that has been a great learning experience. I'm still super nervous to
look at the stats after each issue but I'm excited about the next steps and
future plans we have for the spring.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/02/helsinki-bay.jpg" class="kg-image" />
</figure>
<h2 id="-3-moving-abroad">(3) Moving abroad</h2>
<p>
The last of my major goals didn't become a reality. My work at Futurice kept
me in Finland for now since I'm doing so many local activities in Helsinki and
some in Tampere. I was able to travel a bit for conferences and spend some
time on our Berlin and Stockholm offices.
</p>
<p>
Before joining my current company, I explored a lot of different options in
Europe but none of them ended up happening. After some interesting
discussions and job opportunities I turned down this fall, I'm getting a bit
more confident and excited about what's out there. While moving away from
Finland won't be one of my goals for 2020, intentionally working on multiple
things that will help me move closer to being able to life and work location
independently are on my list.
</p>
<p>
Location-independence (or remote-first) has been a topic that I have been
exploring a lot lately. My goal is that before I turn 35 (luckily still few
more years to go!), I have reached a situation in life where I can life and
work without being tied down to a specific location.
</p>
<h2 id="-1-communities">(+1) Communities</h2>
<p>
This year I mostly continued my work in existing communities. With Turku <3
Frontend, we celebrated the 4th anniversary in December, hosting another 9
meetups this year and welcoming new companies to the town. With codebar, I
coached in various workshops and hosted a hackathon together with them at the
end of the summer.
</p>
<p>
With our Futurice internal developer community, we hosted weekly
<a href="http://techweeklies.futurice.com/">Tech Weeklies</a> meetups and a
Global Code Camp hackathon in Tallinn where we learned to hack with emerging
technology.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/02/helsinki-dev-lunch.jpg" class="kg-image" />
</figure>
<p>
One new thing that I did start this year was Helsinki Dev Lunch. As I often
normally eat lunch with my friends or colleagues, I wanted to meet new people
and to bring them together to meet each other. So I started inviting people to
an open lunch on social media and throughout the fall, we had developers, data
scientists and students joining together once a month to eat lunch and to talk
about stuff. (I you're interested in joining, follow me on
<a href="https://twitter.com/hamatti">Twitter</a> or
<a href="https://www.linkedin.com/in/juhamattisantala">LinkedIn</a> and I'll
post about them once a month before the lunch.)
</p>
<p>
One of my failed community goals for this year was that I didn't get to launch
Code Parrot. I did have some discussions with bunch of people over the summer
about it and throughout the year was looking for a great opportunity and the
best approach but other things - like my new job - took my focus and Code
Parrot needs to be postponed once more.
</p>
<h2 id="-2-all-that-jazz">(+2) All that jazz</h2>
<p>
So everything above was pretty much work related (or "work-like hobbies" as
one of my occupational nurses described my life when I told about my hobbies).
That's because most of my life is that. Once your passions and hobbies turn
into full-time work, it's kinda bound to happen and I don't regret it a single
bit. I'll take working on things I love and having less hobbies over working a
horrible job and having great hobbies any day. I'm extremely lucky that I
don't have to work to finance my hobbies. My hobby finances itself.
</p>
<p>
But other things did happen too in my life. Once again, I played a bunch of
video games. Link's Awakening remake for Switch and Super Mario Maker 2 were
some amazing highlights of 2019. Other than them, I mostly played older games.
I replayed Firewatch twice, continued putting in hundreds of hours into FIFA,
NHL and NBA games and played Super Mario World and Link to the Past like a
lot.
</p>
<p>
This year turned out to be one that will forever be remembered in history.
Finland managed to make its way into men's UEFA Euro tournament for the first
time in history and next summer we'll be playing with the big boys in Denmark
and Russia.
</p>
<p>
I also got a bunch of new friends into my life through work, meetups and
conferences and I am so happy about them. If we become buddies this year and
you're reading this: I like you and I'm happy I met you ❤️.
</p>
<h2 id="-1-not-all-roses">(-1) Not all roses</h2>
<p>
While this year was full of great moments and accomplishments, it wasn't all
just happy moments. As the year is pretty much done, this Christmas holiday
couldn't have come on a better time. I'm beyond tired and seriously need to
catch my breath before embarking to the new decade.
</p>
<p>
I ended up working a lot and stressing about work a lot too this year,
especially after starting in my new position. It was originally agreed to
start as a 6-month experiment (which to my delight was continued and now is a
real permanent role) which caused me to put everything I had into it to show
that it's a valuable role and that I'm the right person to do the job.
</p>
<p>
I also had some positive problems as I had to really think about where I want
to take my career as I was discussing with multiple companies about developer
advocacy positions throughout the fall. For now, I ended up staying as I
believed not many company can right now provide me the level of autonomy and
flexibility that my current job does.
</p>
<p>
But those discussions did put some extra stress on my shoulders and this
Christmas holiday I have planned time to once again think about which
direction I want to take my current job into to make sure it doesn't become
"just a job" but actually something that helps me drive things that are
important for me.
</p>
<p>
I also tried a lot to start making video stuff. Video is the medium of this
decade (and even more so next decade) and after struggling with the idea a
lot, this year I started to experiment. I started
<a href="https://hamatti.org/youtube">a Youtube channel</a> and continued
<a href="https://twitch.tv/hamatti">streaming on Twitch</a> but neither one
really felt right nor went anywhere.
</p>
<p>
Filming and editing was something I learned just a bit this year but not
enough to get comfortable enough to get into the workflow of
shoot-and-publish. I started recording one Javascript debugging video in July
and it's still on my editing process because I can't get over the things I
don't like about it.
</p>
<h2 id="2020-here-we-come">2020, here we come</h2>
<p>
Looking back at this year, 2019 was amazing year of personal and professional
growth, dreams coming true and life being great. There are great things in
progress also for 2020 and I hope to tell more about them soon.
</p>
<!--kg-card-begin: markdown-->
<hr />
<!--kg-card-end: markdown-->
<p>
If you’re looking to do some end-of-year reflections but don’t know what to
think about, check out
<a href="https://yearcompass.com/" rel="noopener nofollow">Year Compass</a>.
This was my fifth year doing it and I absolutely love it.
</p>
Lightning talks
2019-12-24T00:00:00Z
https://hamatti.org/posts/lightning-talks/
<p>
Let me start by saying that I love conferences. But speaking in conferences is
not easy. First of all, it's quite intimidating in the beginning to talk to
hundreds of experts in your industry. Second, it's a numbers game with very
difficult odds. For example, JSConf EU (to which I submitted proposals as
well)
<a href="https://twitter.com/jsconfeu/status/1089897872537968640">had almost 1000 proposals this year</a>
with only 50 slots.
</p>
<p>
This year was my big break into conference speaking. Out of my about 50
proposals, 4 of them were accepted and I ended up giving 2 talks, 1 panel
discussion and 2 lightning talks in international conferences during 2019. I'm
so grateful to PyCon CZ, PyCon Estonia and PyCon Sweden for trusting in me and
finding my talks interesting to their communities.
</p>
<p>
I love talks as a form of sharing information. At best, they provide you with
new ideas, new approaches and new tools to become better at your craft. In
addition, they can be very inspiring and entertaining, bringing extra value.
And most importantly, they spark discussion. After each of my talks, I've had
great discussions with dozens of people participating in these events about
the topics, learning from them and hearing their opinions.
</p>
<p>
One format I like a lot in conferences are lightning talks. If you're not
familiar with the term, lightning talks are short, often 5 minutes, talks done
ad-hoc at the conference by anyone who's participating. You just sign up your
name on a paper at the venue and you get 5 minutes with a mic and slides.
</p>
<p>
5 minute ad-hoc talk is a wonderful concept. It's short enough that you don't
even need slides. You can present a conference or community in your area,
showcase a project you made, sing a song or - like I did in PyCon CZ - talk
about your travels and mental health.
</p>
<p>
I wish more people would participate in lighting talks. In some conferences,
they are fully booked in minutes after the signup board appears and on others,
they might not even get full at the end.
</p>
<p>
For me, lightning talks offer an opportunity for the community to come
together in a very casual way. They are a nice change of pace in the
conference and they give an opportunity for anyone to speak without having to
send dozens of applications to get accepted. I highly recommend trying them
out if you attend conferences.
</p>
My journey in diversity and inclusion in tech
2019-08-28T00:00:00Z
https://hamatti.org/posts/diversity-and-inclusion-in-tech/
<p>Some people who know me personally well and have been involved in my professional career for the past 4 years, know that I'm very passionate and increasingly vocal about diversity and inclusion in tech. Others, who have known me for longer, might see me as a hypocrite.</p>
<p>I haven't always been so passionate about it and I want to share my story. It's not an easy one to share and quite frankly, publishing this post is little bit nervewrecking. But it's so much less that than what other people have to endure sometimes in this industry.</p>
<h2>Backstory</h2>
<p>Let me tell you little bit about me before 2014. I grew up little bit socially clueless, spending8 most of my time in online forums and IRC channels. Many aspects of my social behaviour were affected by culture where saying things like "There are no women in Internet" was a norm or the dominant way of thinking was that men were superior to women. I learned that culture.</p>
<p>That obviously leaked into my real life. I would like to say I made inappropriate jokes but there was nothing joke-like there. I was an asshole. I didn't realize it back then. For me, it's was just witty banter. I can't remember if people pointed it out. Maybe they did and I just shrugged it off. Maybe they didn't because it's hard to say about those things that to a friend.</p>
<p>I have always been very competitive in everything in life. In 2014, I moved to San Francisco to work in a small startup. In March, we hired our first developer who was a woman. I made stupid remarks in a group chat of some fellow startup people, something along the lines of "now I need to really start focusing on my work so I don't lose to her".</p>
<p>Then the best thing happened. <strong>I got called out for it</strong>. I didn't realize it then well enough. I tried to defend my position and explain my way out of it. I wasn't able to see how my thinking and actions could affect things outside me.</p>
<p>None of my bullshit behaviour back then is excusable by anything.</p>
<p>Living in San Francisco and being in the center of so much more discussion about diversity and inclusion problems in tech slowly started to make me understand things from another angle. And then, I started to do my best to make things better.</p>
<h2>Why diversity and inclusion matter?</h2>
<p>There are many reasons to promote diversity and inclusion in technology industry. There are business reasons and studies that say how diverse teams are better performing ones. For me, one of the reasons (other than being a decent human being) is this:</p>
<blockquote>
<p>We are building products, tools and services for the humankind. There are so many different kinds of people in the world that if everyone in the team building these tools are too similar, we fail to build services that take into account different kind of people.</p>
<p>Maybe 20 years ago, technology was an <em>opt-in</em> - you could choose to use and interact with it. 2019, that's no longer the case. In many societies in the world, it's very difficult to even be a member of the society if you are not computer literate.</p>
<p>That's why I think it's crucially important that we bring in people from different backgrounds, different life situations (I think career-changers into tech is a wonderful thing) and different ways of seeing the world.</p>
</blockquote>
<p>You might not agree with the above and that's perfectly fine. I don't claim to know that I'm right. But I want to be clear on why it matters to me.</p>
<p>I also keep fighting because I've been to the other side. I feel the pain in me when I see people treat others inappropriately. And I keep fighting because I hope that while I can never make my past actions acceptable, I can hopefully make the future a better place for many people.</p>
<p>As an industry, we have so much work to do. And it all starts by realizing and accepting that it's not about "us good people" and "them bad people". Even the good companies, good events and good communities have issues. We cannot turn a blind eye on them. We need to all strive to make our culture more accessible and more inclusive to different people.</p>
<p>I still have lot to learn and lot of work to do in my own networks. One of the developer communities I ran, Turku <3 Frontend, is quite homologous because I have been bringing in people from my limited networks. I want to expand those networks and make sure that we are a community that is a great platform for everyone to learn and grow, not just people who happen to be like me and hang out with me.</p>
<p>And most importantly, I need to also be better at treating people with the respect that they deserve and to make sure that whenever mistakes are made, that I own up to them. Because I know I will make mistakes.</p>
Why I love using command line interface?
2019-07-27T00:00:00Z
https://hamatti.org/posts/why-i-love-command-line/
<p>My Saturday started by waking up to <a href="https://twitter.com/Jarkko_Moilanen/status/1154988415244472321?ref_src=twsrc%5Etfw">a Twitter thread</a> I was tagged into:</p>
<blockquote>
Why do you love (or do you?) the old school command line interface? What are the best use cases for it in API related work? <a href="https://twitter.com/janik6n?ref_src=twsrc%5Etfw">@janik6n</a> <a href="https://twitter.com/Hamatti?ref_src=twsrc%5Etfw">@Hamatti</a> <a href="https://twitter.com/anttiviljami?ref_src=twsrc%5Etfw">@anttiviljami</a> <a href="https://twitter.com/SvenWal?ref_src=twsrc%5Etfw">@SvenWal</a> <a href="https://twitter.com/agraebe?ref_src=twsrc%5Etfw">@agraebe</a> <br /><br />Asking some opinions and might take some to future posts in <a href="https://twitter.com/hashtag/100DaysDX?src=hash&ref_src=twsrc%5Etfw">#100DaysDX</a> <a href="https://twitter.com/hashtag/Devrel?src=hash&ref_src=twsrc%5Etfw">#Devrel</a> <a href="https://twitter.com/hashtag/developer?src=hash&ref_src=twsrc%5Etfw">#developer</a> <a href="https://twitter.com/hashtag/CLI?src=hash&ref_src=twsrc%5Etfw">#CLI</a><p></p>— ᴊᴀʀᴋᴋᴏ #dxdoctor ᴍᴏɪʟᴀɴᴇɴ (@Jarkko_Moilanen) <a href="https://twitter.com/Jarkko_Moilanen/status/1154988415244472321?ref_src=twsrc%5Etfw">July 27, 2019</a></blockquote>
<p>Tweeting out my answers, I started to come up with more and more reasons, so I figured I'll also document them down here in my blog and open up each point a little bit in detail.</p>
<h2>1) You can replicate commands</h2>
<p>The first benefit that came to my mind when I started thinking about this, was replicating commands. If I use a GUI, to replicate some complex action, I need to do a lot of clicking around, whereas using command line, I can run the command with one copy-paste command (or using history feature) as many times as I want without having to do any more work.</p>
<p>Replicating commands provides a way to upfront the amount of work that needs to be done. Sometimes it's bit more but quite often, if you run the command more than once, you start winning time and effort.</p>
<p>Replication has other benefits too. Let's say I want to download 100 Youtube videos:</p>
<h3>Using a GUI</h3>
<p>With a GUI (of an imaginary downloader software), I have to copy-paste the urls one at the time, click Download, wait and repeat.</p>
<p>Oh wait, what if I realize that the application downloaded all videos with default 240p quality? Maybe I go to settings, change a quality setting to 1080p and start pasting in those URLs again.</p>
<h3>Using a CLI</h3>
<p>With a CLI, I can dump all 100 URLs to a file and run a download command for each line in that file. With one command, I just downloaded 100 files.</p>
<p>And if I need to change the quality option, I can just add an option flag to my loop, rerun that one command and wait as my videos are downloaded.</p>
<h2>2) History</h2>
<p>Second one of my favorite aspects about command line interfaces is that I get access to history of commands. I can easily repeat the previous command or search for any command I have run before.</p>
<p>With most GUI applications, I have used, this is not possible. Some photo or video editing software has repeatable history but most software doesn't.</p>
<p>I personally use <a href="https://dev.to/hamatti/better-bash-history-search-with-mcfly-3kck">mcfly</a> as my bash history search app. With it, I can quickly find and re-run commands that I have done before.</p>
<h2>3) Chain commands</h2>
<p>If a CLI application is designed nicely, it takes input from stdin and outputs to stdout. Of course not all CLI apps do this and for some apps it doesn't even make sense really.</p>
<p>But many do. And that design allows for chaining commands. You can take your toolbox of bash tools for example and mix-and-match them however you want.</p>
<p>For example, let's say we are interested in Pokemon TCG cards (if you've been reading my blog, it should be quite obvious I'm a huge Pokemon fan). We want to know every unique card name that includes 'Charizard':</p>
<pre><code class="language-bash">curl https://api.pokemontcg.io/v1/cards?name=Charizard | jq '.cards | .[] | .name' | sort | uniq
</code></pre>
<p>Here we curl'd an API, which returns a JSON into the stdout. We then pass that JSON to <a href="https://shapeshed.com/jq-json/">jq</a>, that reads JSON from stdin and allows us to do operations on it. In this case, we access the <code>cards</code> property and get all the <code>name</code> properties. Once again, we output that to stdout so that <code>sort</code> can read it, sort alphabetically and finally <code>uniq</code> removes duplicates.</p>
<p>Some GUI software aimed for interacting with HTTP calls allows this kind of operation built-in. But the beautiful thing with the command line is that we can combine any tools we want. You don't have to plan for every possible use case when building the tool: as long as you read from stdin and print to stdout, your CLI app can be combined with any other.</p>
<h2>4) Programmability</h2>
<p>An extension of the replicability in #1 is that we can alter conditions and parameters of calls programmatically. Let's expand on the downloading Youtube video examples from #1.</p>
<p>Maybe this time we get a mix of different sources, not just Youtube. You can use basic conditionals within your script to call different applications based on the source or filetype.</p>
<p>Maybe you pass Youtube links to Youtube downloader, image and txt file links to curl or wget and some other proprietary formats to specific software that allows extracting the information.</p>
<h2>5) Share commands</h2>
<p>How many times have you helped someone remotely to use their computer for something? Maybe it's a friend who needs to install some software or maybe it's a colleague trying to setup their development environment. Or maybe you're writing a blog post teaching people how to use a software.</p>
<p>With GUI apps, it's often bit vague "click on the blue button in the upper right corner that says Scan" verbal instructions or screenshots/screencasts. The instructions grow quite long and take a disproportional amount of time to write down.</p>
<p>With CLI apps, you can just copy-paste the commands and send them to the one in need or add them to your blog. Please be careful though: running commands that someone else tells you to run is very dangerous. You should never blindly run commands anyone tells you to run without understanding what they do. I have seen some havoc that has been done to systems by misinformed users.</p>
<h2>6) Automation / scheduling with cron</h2>
<p>The universality of these command line tools means that you don't have to build every feature to every application. If we continue with our example of downloading Youtube videos:</p>
<p>You know that your favorite creator uploads a new video every Monday at 10.00. You could set yourself a reminder to download it with your GUI app manually or hope to find a GUI app that downloads Youtube videos <strong>and</strong> allows scheduling.</p>
<p>Or you can use the CLI downloader and combine that with <a href="https://en.wikipedia.org/wiki/Cron">cron</a>. Now you can use one app to find the URL to the latest video, feed that to the downloader and schedule it all to happen every Monday at 10.05.</p>
<p>Cron is great for maintenance tasks that are essentially the same task run over and over again on regular intervals. Like doing backups, cleaning up some upload folders, etc.</p>
<h2>7) Less change in interface</h2>
<p>I left this one for last because it's a bit different and less universally true. I have noticed that GUI applications quite more often make changes to their UIs than CLI applications to their API. That makes using history and written-down instructions much better at lasting time.</p>
I love writing scripts to solve small problems
2019-06-23T00:00:00Z
https://hamatti.org/posts/i-love-writing-small-scripts/
<p>One of the reasons I like programming so much is that it allows me to automate small and annoying things that would otherwise require bunch of manual work.</p>
<p>Yesterday, I downloaded a set of files that came in a following directory structure inside a zip file:</p>
<pre class="line-numbers language-bash"><code class="language-bash">- Main Folder
- Theme A (1)
- FileA.pdf
- FileA.txt
- FileA.jpg
- Theme B (2)
- FileB.pdf
- FileB.txt
- FileB.jpg
- Theme C (3)
...
</code></pre>
<p>For my use case though, I was only interested in the pdf files and wanted to record the order of those files that was written in parentheses in the folder. I could have manually moved them all to a new folder in Finder but since there were a few dozen of them, I opened my editor and started writing Python.</p>
<pre class="line-numbers language-python"><code class="language-python">import os
import re
import shutil
NUMBER_PATTERN = re.compile(r'\((\d+)\)')
for directory, _, files in os.walk('.'):
if directory == './Output':
continue
for filename in files:
if not filename.endswith('.pdf'):
continue
episode_number = NUMBER_PATTERN.findall(directory)[0]
path = os.path.join(directory, filename)
new_filename = f'{episode_number:0>2} - {filename}'
new_path = os.path.join('Output', new_filename)
print(f'Copying {path} to {new_path}')
shutil.copyfile(path, new_path)
</code></pre>
<p>It's a single-run script that relies on a very specific naming and file structure as well as the existence of Output folder. So if something's out of order, it just breaks.</p>
<p>That means it's not very maintainable and it probably isn't the best nor most pythonic code I could write. But since it's a script meant to run once in this very particular situation, I can recover from error situations manually.</p>
<p>And that's the beauty of it. It doesn't have to be good code, it just has to work once. It saves me lots of annoying manual copying and renaming files.</p>
<p>As opposed to the quest of writing good, maintainable and error-resistant code at work for production, these scripts allow me to get small wins by just scraping some code together.</p>
<p><strong>edit</strong> I want to highlight this beautiful bash script that @teroyks created <a href="https://dev.to/teroyks/comment/c9im">in the comments of this post in DEV</a>:</p>
<pre class="line-numbers language-bash"><code class="language-bash">find . -iname "*.pdf" | while read F; do FILE=$(basename "$F"); NR=$(printf "%02d" "$(echo "$F" | sed "s/.*(\(.*\)).*/\1/")"); cp -v "$F" "./Output/$NR - $FILE"; done
</code></pre>
<p><strong>edit2</strong> @teroyks also provided us an example in fish shell:</p>
<pre class="line-numbers language-bash"><code class="language-bash">for f in (find . -iname "*.pdf")
set file (basename $f)
set number (string match -r "\((.*)\)" $f)[2]
set number (printf "%02d" $number)
cp -v $f "./Output/$number - $file"
end
</code></pre>
Simple backend system for frontend workshops in Javascript
2019-05-29T00:00:00Z
https://hamatti.org/posts/simple-backend-system-for-frontend-workshops-in-javascript/
<p>I coach in many programming workshops. If you have coached in one too, you might have noticed that it's challenging to find the right balance between depth and width. Especially in frontend workshops, I don't want to confuse students with building backends or API systems but I do want to provide them easy to use and extendable system.</p>
<p>I built a starter kit based on <a href="https://github.com/facebook/create-react-app">create-react-app</a> that doesn't touch the basic React side of the frontend but adds a backend, couple of commands and an API client so that the user doesn't have to worry about doing HTTP calls themselves.</p>
<p>You can find my starter kit code from <a href="https://github.com/Hamatti/cra-with-json-server">Hamatti/cra-with-json-server</a></p>
<h2>My workshop philosophy</h2>
<p>There are many ways to teach. Some take a theory-first approach and only advance to next thing when something has been sufficiently learned. It's a great approach for long-term learning but in short workshops (like a two-day workshop over a weekend), you end up not getting much done.</p>
<p>I like the way <a href="https://railsgirls.com/">Rails Girls</a> does it and my philosophy has been very much influenced by it. You get a lot of things done by taking advance of pre-built stuff. The point of the workshop is not to master things but to get interested, see all the different parts of building a project and deploying something to cloud to show to your friends.</p>
<p>When you then want to learn more, you can come back to certain steps and learn more about that.</p>
<h2>Backend Storage</h2>
<p>Luckily, a friend of mine, <a href="https://twitter.com/jelmnai">Johannes</a>, introduced me to <a href="https://github.com/typicode/json-server"><code>json-server</code></a>. It's a wonderful npm package that provides you with HTTP endpoints into data stored in a JSON file.</p>
<p><code>json-server</code> is a tool that provides a lot more than what we're gonna go through today. It's great for prototyping and mocking APIs with a huge offering of endpoints. Today, we are mainly interested in GET, POST, PATCH and DELETE endpoints.</p>
<p>As mentioned earlier, my first approach (I'm hoping to make it easier to plug in to any system later) was using create-react-app, so if you want to follow along, create a new app with</p>
<pre class="language-bash"><code class="language-bash">create-react-app my-new-project</code></pre>
<p>and install <code>json-server</code></p>
<pre class="language-bash"><code class="language-bash">npm install -g json-server</code></pre>
<p>Next we create a JSON storage into <code>backend/db.json</code>. I also added a <code>backend/readme.md</code> that explains how to use it. Your <code>backend/db.json</code> can either start as an empty JSON</p>
<pre class="language-json"><code class="language-json">{}</code></pre>
<p>or if want to set it up for a tutorial, you can pre-populate it with collections you want. A collection in this case is just a key with an array of objects.</p>
<pre class="language-json"><code class="language-json">{
"authors": [
{
"name": "Juhis",
"username": "hamatti",
"email": "juhamattisantala@gmail.com"
}
]
}</code></pre>
<p>If you then run</p>
<pre class="language-bash"><code class="language-bash">json-server backend/db.json -p 3001</code></pre>
<p>you will gain API access into <a href="http://localhost:3001/">http://localhost:3001</a>. You can try this by running a curl in a new terminal tab to fetch data:</p>
<pre class="language-bash"><code class="language-bash">curl http://localhost:3001/authors</code></pre>
<p>You should see</p>
<pre class="language-json"><code class="language-json">[
{
"name": "Juhis",
"username": "hamatti",
"email": "juhamattisantala@gmail.com"
}
]</code></pre>
<p>in your terminal.</p>
<p>To make it simpler, I added a npm script to <code>package.json</code> to run it.</p>
<pre class="language-json"><code class="language-json">"scripts": {
"backend": "json-server ./backend/db.json -p 3001"
}</code></pre>
<p>Now you'll be able to start the backend API with <code>npm run backend</code> and you don't have to know what happens behind the scenes.</p>
<h2>API</h2>
<p>In <code>src/api/apiClient.js</code>, I've created basic getters and setters to access the data in API:</p>
<pre class="language-javascript"><code class="language-javascript">import axios from "axios";
const config = {
baseURL: "http://localhost:3001",
headers: {
"Content-Type": "application/json",
},
};
const client = axios.create(config);</code></pre>
<p>I'm using axios for calls and create a client with the configuration. After the setup, I have these functions for interacting with the API.</p>
<pre class="language-javascript"><code class="language-javascript">export function getAuthors(ctx) {
return client.get("/authors").then(({ data }) => {
ctx.setState({
authors: data,
});
});
}
export function updateAuthors(id, data) {
return client.patch(`/authors/${id}`, data);
}
export function createAuthors(data) {
return client.post(`/authors`, data);
}</code></pre>
<p>With this setup, an user should never have to touch <code>backend/</code> folder nor the <code>src/api/</code> folder, they just need to import them where they wish to query the API.</p>
<p>The first function, <code>getAuthors</code> is currently very React specific: it expects a parameter to be <code>this</code> of a stateful React component and it saves the data directly into the state.</p>
<h2>Build new endpoints</h2>
<p>A basic workshop can be done with just pre-defined data structure and endpoints but it's most fun when people can decide themselves what data to add.</p>
<p>So let's add a functionality to create that on the fly. <strong>This script is currently not the most error-proof: if you run it twice with the same input, it will break.</strong></p>
<p>In <code>package.json</code>, I added a new script:</p>
<pre class="language-json"><code class="language-json">"scripts": {
"generateAPI": "node generateAPI.js"
}</code></pre>
<p>and the file <code>generateAPI.js</code>:</p>
<pre class="language-javascript"><code class="language-javascript">"use strict";
const fs = require("fs");
const DB_PATH = "backend/db.json";
const API_CLIENT_PATH = "src/api/apiClient.js";
let key = process.argv[2];
if (!key) return;
key = key.toLowerCase();
let originalData = JSON.parse(fs.readFileSync(DB_PATH));
originalData[key] = [];
// Write to file
fs.writeFileSync(DB_PATH, JSON.stringify(originalData));
const titleCase = `${key.charAt(0).toUpperCase()}${key.substr(1)}`;
const newFunctions = `export function get${titleCase}(ctx) {
return client.get("/${key}").then(({ data }) => {
ctx.setState({
${key}: data
});
});
}
export function update${titleCase}(id, data) {
return client.patch(\`/${key}/\${id}\`, data);
}
export function create${titleCase}(data) {
return client.post("/${key}/", data);
}`;
const originalApiClient = fs.readFileSync(API_CLIENT_PATH);
const newApiClient = `${originalApiClient}\n\n${newFunctions}`;
fs.writeFileSync(API_CLIENT_PATH, newApiClient);
console.log(`${key} was added into the database and API.
You can now import new functions with
import { get${titleCase}, update${titleCase} and create${titleCase} } from './api/apiClient';
and you will have access to the data.`);</code></pre>
<p>You run it with <code>npm run generateAPI [model]</code> and it adds <code>model</code> into <code>backend/db.json</code> as a <code>"model": []</code> collection as well as generates <code>get[Model]</code>, <code>update[Model]</code> and <code>create[Model]</code> functions into <code>src/api/apiClient.js</code>.</p>
<p>It's a rather hacky system for now but with my testing, it works quite nicely. With a npm script command, you can add new collections into your storage and just import the new functionality from <code>apiClient.js</code>.</p>
Make your function calls more readable
2019-05-23T00:00:00Z
https://hamatti.org/posts/make-your-function-calls-more-readable/
<h2>Introduction</h2>
<blockquote>
<p>“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write.” - Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship</p>
</blockquote>
<p>Writing code that is easy to understand, modify and extend is a good goal for a software developer. But it's hard. So hard that multiple books have been written about the topic and people do conference and meetup talks about it all the time around the world.</p>
<p>If you have been writing or reading a lot of Javascript, you have probably seen function calls like these:</p>
<pre class="language-javascript"><code class="language-javascript">getUnicorns(10, 5, 10, true);
getUnicorns(7);</code></pre>
<p>This code is very hard to read. What do those numbers and booleans mean?</p>
<p>These functions grow up in the wild. Maybe it started as a very simple function and with new requirements, people just added these arguments one by one and finally someone with fresh look at the code feels very confused. Or maybe it started with four arguments but the one who wrote it and immediately after that called, knew what they wanted to get done and had everything fresh in mind.</p>
<p>Regardless of how it happened, there's a problem. In the future, someone (another person or even the future you) will encounter this code and to understand what's going on, they have to do research and keep a lot of things in mind. All of this is distracting them from building quality code.</p>
<p>Unreadable code attracts unreadable code (and bugs 🐜).</p>
<h2>How does Python do it? Named arguments</h2>
<p>Learning from another languages can be a great way to help us become better developers. At the same time, it's important to stick to idiomatic style for the code that you write. So it's an elegant balance.</p>
<p>In Python, there's a mechanic called <em>named arguments</em> (also known as <em>keyword arguments</em>) which are arguments that when the function is called, must be named.</p>
<pre class="language-python"><code class="language-python">def get_unicorns(amount, min_age = 0, max_age = 100, only_magical = false):
pass
get_unicorns(10, min_age=5, max_age=10, only_magical=true)</code></pre>
<p>Much easier to read!</p>
<h2>Let's make our JS code better</h2>
<p>Unfortunately we don't have named arguments in Javascript. However, with the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring">ES6 Object Destructuring</a>, we can do something similar.</p>
<h3>A quick primer on Object Destructuring</h3>
<pre class="language-javascript"><code class="language-javascript">
/* Extract properties you're interested in */
const { name, age } = unicorn
/* is the same as */
const name = unicorn.name
const age = unicorn.age
/* ----- */
/* Give default values to destructured properties */
const { name, age = 5 } = unicorn
/* which is same as */
const name = unicorn.name
const age = unicorn.age !== undefined ? unicorn.age : 5
/* ----- */
/* Give default value to function parameter */
function getUnicorns(amount, options = {})</code></pre>
<h3>"Named arguments" in Javascript</h3>
<p>Using our knowledge of object destructuring, let's make our original code more readable.</p>
<pre class="language-javascript"><code class="language-javascript">
function getUnicorns(
amount,
{ minAge = 0, maxAge = 99, onlyMagical = false } = {}
) {}
getUnicorns(10, { minAge: 5, maxAge: 10, onlyMagical: true });
getUnicorns(7);
getUnicorns(5, { onlyMagical: true });</code></pre>
<p>Just like in our Python example, reading through code like this is much more enjoyable and we can focus on important pieces: the logic and goals.</p>
<p>In addition to improved readability, another benefit of this object destructuring approach is that you don't have to worry about the order of arguments or even the existence of them all.</p>
<p>If you feel like destructuring in function definition gets crowded, especially with default values, you can extract that into the function body:</p>
<pre class="language-javascript"><code class="language-javascript">
function getUnicorns(amount, opts = {}) {
const { minAge = 0, maxAge = 99, onlyMagical = false } = opts;
/* rest of the function body here */
}</code></pre>
<p>A downside of the previous example that you lose visibility on the default values and options available in your editor's inline autocomplete or docs.</p>
<h2>Further improvement with Typescript</h2>
<p>If you're using Typescript, you can gain a lot more with this approach.</p>
<pre class="language-javascript"><code class="language-javascript">
type UnicornFilter = {
minAge?: Number,
maxAge?: Number,
onlyMagical?: Boolean,
};
function getUnicorns(
amount: Number,
{ minAge = 0, maxAge = 99, onlyMagical = false }: UnicornFilter = {}
) {}
getUnicorns(10, { minAge: 5, maxAge: 10, onlyMagical: true });
getUnicorns(7);
getUnicorns(5, { onlyMagical: true });</code></pre>
<p>By defining the type as <code>UnicornFilter</code>, we gain a couple of things:</p>
<ol>
<li>you don't end up calling the function with arguments that shouldn't be there</li>
<li>you pass in correct values for arguments</li>
<li>the autocomplete in your editor becomes better because it knows what to expect.</li>
</ol>
<h2>Conclusion</h2>
<p>Keeping readability in mind when you are working on code is worth the challenge. Usually things rot when small changes pile up over time. Starting simple and then being asked to "just add this one thing", you don't necessarily think about the long-term readability.</p>
<p>Be brave to refactor your code when you add small things to it. It's always easiest to do when the code and what it does is fresh on your mind rather than the time in 6 months when your new team member is looking at it and trying to figure out what it's supposed to do.</p>
How to enable SSL in Netlify with custom domain
2019-02-14T00:00:00Z
https://hamatti.org/posts/how-to-enable-ssl-in-netlify/
<p>Do you have a static site you need to put somewhere in Internet so others can access it? You're in for a treat: <a href="https://www.slant.co/topics/2256/~best-static-website-hosting-provider">there are dozens of really good options</a>. I wanted to try out <a href="https://netlify.com/">Netlify</a> for my website renewal because everyone had been saying good things about it and I had never used it.</p>
<p>I had an existing domain on <a href="https://hover.com/">Hover</a> and used to run my website from a self-hosted <a href="https://hetzner.de/">Hetzner VPS</a>. But during the past decade, the website and my server had become a mess. I wanted to start using a static site generator and to make deployment easier. So I installed <a href="https://www.11ty.io/">Eleventy</a>, piece by piece transformed my old site into an Eleventy site and ran <code>eleventy</code>. Boom, few seconds later I had <code>_site</code> folder that I dragged and dropped into Netlify and the site was up.</p>
<p><strong>It was one of the best UX experiences ever.</strong></p>
<p>Netlify offers Let's Encrypt SSL certificates for websites but that's when I hit some issues. I'm not an expert on domains and DNS and whatever I tried, I was always faced with an issue.</p>
<blockquote>
<p>Web sites prove their identity via certificates. Firefox Developer Edition does not trust this site because it uses a certificate that is not valid for <a href="http://hamatti.org/">hamatti.org</a>. The certificate is only valid for the following names: *.netlify.com, <a href="http://netlify.com/">netlify.com</a><br />
Error code: SSL_ERROR_BAD_CERT_DOMAIN</p>
</blockquote>
<p>After lots of reading docs, googling, consulting much smarter colleagues and finally contacting Netlify support, things got fixed.</p>
<p>Their documentation on <a href="https://www.netlify.com/docs/custom-domains/#dns-configuration">setting up DNS with custom domain</a> is pretty good. As I use Hover, they don't provide ANAME or ALIAS options so I had to do A record and CNAME.</p>
<pre><code>A @ 104.198.14.52
CNAME www [appname].netlify.com
</code></pre>
<p><code>104.198.14.52</code> is Netlify's load balancer's IP address and <code>[appname]</code> should be your app's name in Netlify.</p>
<p>So I set up all this but still encountered issues. After contacting support, turned out that sometimes (probably me being too eager to change the settings too fast) something in DNS-and-or-certificate chain fails and it hungs on Netlify's end.</p>
<p>If that happens to you, contacting <a href="https://www.netlify.com/support/">their support</a> is the way to go. They can manually clear the issue and re-trigger the fetch to make it work.</p>
PHP needs its own ES6
2019-02-07T00:00:00Z
https://hamatti.org/posts/php-needs-its-own-es6/
<p>I have a love-hate relationship with PHP. I have written PHP in many forms from website templating and Wordpress to full MVC and SPA backend solutions in the past 15+ years.</p>
<p>I was reading through Bronson Dunbar's post <a href="https://bronsondunbar.com/using-and-learning-reactjs-for-2-years-what-have-i-learnt/">"Using and learning ReactJS for 2 years, what have I learnt</a>, and I stopped at this:</p>
<blockquote>
<p>As we mentioned earlier, JavaScript has been around for some time, and in order to stay relevant a few people decided it was time to give it an update and that is when ES6 was born.</p>
</blockquote>
<p>Both Javascript and PHP have similarities in their journey. Neither one was built for what they are used now: Brendan Eich wrote the Javascript as a prototype in 10 days back in 1995 to provide Netscape interaction into browser and Rasmus Lerdorf wrote PHP to be a templating engine in 1994. Due to the popularity of both, they have evolved into something completely different.</p>
<p>For the past few years (after getting over the Python 2->3 pain), I've been thinking and speaking about how I want PHP to break backwards compatibility. I know it's not gonna happen because such a big part of Internet runs on PHP and it would break everything.</p>
<p>So Bronson's post gave me something to think about: maybe we don't need a "new PHP", maybe we need <strong>ES6-for-PHP</strong> — a layer on top of PHP that would allow us to tackle the issues and write different PHP while still being compatible under the hood.</p>
<h2>Background</h2>
<p>I'm no language designer nor someone who finds joy (nor has skills) in building new programming languages. But I'm a dreamer and I can dream.</p>
<p>One of the big annoyances in PHP is the inconsistent standard library. Which is actually a feature, not a bug. When Rasmus Lerdorf was creating the language, <a href="http://news.php.net/php.internals/70691">he used different kind of naming schemes to balance the function hashing</a>.</p>
<blockquote>
<p>Well, there were other factors in play there. htmlspecialchars was a very early function. Back when PHP had less than 100 functions and the function hashing mechanism was strlen(). In order to get a nice hash distribution of function names across the various function name lengths names were picked specifically to make them fit into a specific length bucket. This was circa late 1994 when PHP was a tool just for my own personal use and I wasn't too worried about not being able to remember the few function names.</p>
</blockquote>
<p>But it's 2019 and a lot of PHP is still being written. What if we could make it more enjoyable? (I love writing Ruby and Ruby on Rails and DHH's <a href="https://rubyonrails.org/doctrine/">The Rails Doctrine</a> is an inspiration to me. Especially the part about developer happiness.)</p>
<p>So what should we work on?</p>
<h2>Consistency-layer on standard library naming</h2>
<p>As you can see from the quote above, PHP's functions were named for specific purpose: to balance the hashing function. It means that as the standard library has grown, it's impossible to remember how to write function names because there's no consistency.</p>
<p>There's <code>strpos</code> but <code>str_rot13</code>. There's <code>php_uname</code> but <code>phpversion</code>. There's <code>strtolower</code> but <code>bin2hex</code>. And there's <code>str_shuffle</code> but <code>recode_string</code>. You can probably see the point.</p>
<p><strong>So first plan of action: creating a consistent and predictable naming scheme</strong></p>
<h2>Turning array functions into methods of arrays</h2>
<p>Let's take a look. Let's say we have an array of values that we want to first filter and then map. In vanilla PHP, we would do this:</p>
<pre class="language-php"><code class="language-php">array_map(
function(number) {
return number * 2;
},
array_filter(
[1,2,3,4,5,6,7,8,9,10],
function(number) {
return number % 2 == 0;
})
);</code></pre>
<p>Notice how <code>array_map</code> has parameters as <code>callback, array</code> and <code>array_filter</code> has parameters <code>array, callback</code>. I have no clue why they are the exact opposite of each other but more often than not, I don't remember which is which and I have to resort back to docs. Also it's difficult to follow because of heavy nesting.</p>
<p>Let's see how we could make it nicer.</p>
<pre class="language-php"><code class="language-php">array(1,2,3,4,5,6,7,8,9,10)
->filter(num -> num % 2 == 0)
->map(num -> num * 2)</code></pre>
<p>Making array functions into methods of array itself, we could chain things. Even if we don't want to adopt ES6-style arrow functions for anonymous functions, it would make this code much easier to follow and ready.</p>
<p><strong>Second plan of action: make <code>array_</code> functions into methods of arrays and make then chainable</strong></p>
<h2>One sort to rule them all</h2>
<p>How about <strong>sorting</strong> then? Currently PHP's sort is a huge mess. Back in 2015, I wrote <a href="https://hamatti.org/posts/whats-with-all-these-sorts-php/">a blog post about my pain with them</a>. Quoting myself:</p>
<blockquote>
<p>There’s of course sort. Instead of giving a flag or parameter to <code>sort</code> to reverse the order, you have <code>rsort</code>. Then if you want to keep the key=>value pairs intact, there’s <code>asort</code> (which has caused me most confusions ever since I accidentally used that one instead of sort) and <code>arsort</code>. Then you can sort with keys <code>ksort</code> and reverse <code>krsort</code>. For natural sorting there’s <code>natsort</code> and case insensitive <code>natcasesort</code>. And when all this is not enough, you can use custom defined comparing function with <code>usort</code>, <code>uasort</code> and <code>uksort</code>. And the inconsistently named <code>array_multisort</code> for multidimensional arrays.</p>
</blockquote>
<p>What if instead, we would have just <code>sort()</code> function and that would work with either flags, keys or custom callbacks. And please have an option for sort that returns array, not just sort as a side-effect. One of the first custom functions I create in most PHP projects is a <code>sorted</code> function (name borrowed from Python) that enables me to be more functional.</p>
<p><strong>Third plan of action: unify sorts</strong></p>
<h2>Separating sequential array and associative array</h2>
<p>Did you know that PHP only has <strong>associative arrays</strong>? It works quite okay while you're in PHP land but when you start converting it to JSON, you start to see issues. <a href="https://hamatti.org/posts/my-love-hate-relationship-with-php-arrays/">Another one of my blog posts from last year</a> highlights this issue.</p>
<blockquote>
<p>If you sort an array with numeric, sequential and unordered keys, you turn from object to an array. If you sort an array with numeric, sequential and ordered keys, it remains an array. Whatever you do for non-numeric or non-sequential array, it will stay an object.</p>
</blockquote>
<p>When I'm reading through code or writing it, I should be able to figure out more consistently what the outcome will be. Using <code>array_values</code> to reset a "once a sequential array that got turned into associative array" is horrible.</p>
<p><strong>Fourth plan of action: separate array types</strong></p>
<h2>Conclusion</h2>
<p>There are probably other parts of the standard library that could benefit from a "ES6 Treatment" but the biggest pain points of my life regarding developing with PHP are these.</p>
<p>Let's recap:</p>
<ol>
<li>Consistent naming</li>
<li>Array functions into chain-able methods of array</li>
<li>One sort, no more</li>
<li>Two arrays are better than one</li>
</ol>
<p>Which parts of PHP would you like to see enhanced with ES6-for-PHP type of solution?</p>
“Event is over and it was a failure. What happened?” — Pre-mortem can help you avoid pitfalls
2019-02-05T00:00:00Z
https://hamatti.org/posts/event-is-over-and-it-was-a-failure-what-happened-pre-mortem-can-help-you-avoid-pitfalls/
<p>In modern day and age, many organizations are adopting <a href="https://en.wikipedia.org/wiki/Retrospective">retrospectives</a> (a session where you look back and talk about what went well, what went wrong and how we can improve) and <a href="https://www.goskills.com/Project-Management/Articles/Project-post-mortem">post-mortems</a> (how did the project go and where did we go wrong) as part of their day-to-day work.</p>
<p>If you haven’t looked into them, I highly suggest you do so. I think especially regular retrospectives can help you build better team work, better project work and improve the conditions around you.</p>
<blockquote>
<p>An ounce of prevention is worth a pound of cure.</p>
</blockquote>
<p>However, in this post I want to bring up something I see much less used but I think brings often even more value. <a href="https://en.wikipedia.org/wiki/Pre-mortem">Pre-mortems</a> are like post-mortems but done before the event. You actively take time to think about possible pitfalls and shortcomings of your event and can build your event in a way that avoids them — before the event even takes place.</p>
<h2>Why pre-mortems?</h2>
<p>The motivation for pre-mortems is a no-brainer in my mind. Imagine the cost (not only direct monetary cost but also reputation, amount of stress and the work you have to do to clean up) of a disaster during an event.</p>
<p>Many problems are not actually world-ending issues but small things that escalate. Things that we could quite easily prevent but we don’t because we didn’t think about them. And the reason we often don’t think about them is that we are obsessed about the <strong>happy path</strong>: how do things go well.</p>
<p>We don’t want to think about bad things or failing. That’s understandable. Especially in a team, we might not want to tell that something that is a responsibility of a teammate could go wrong. After all, we don’t want to doubt others so that they wouldn’t doubt us. But the more open you can be, the better the team’s performance is and the better experience your event will be for participants.</p>
<p><img src="https://hamatti.org/assets/img/posts/event-is-over/plan.jpeg" alt="Photo by Glenn Carstens-Peters on Unsplash" /></p>
<h2>How to do a pre-mortem?</h2>
<h3>Step 1: List what went wrong</h3>
<p>Just like every other methodology, there are books and articles written about how to do things. I have always just started with a very simple approach: book some time for a face-to-face (or Skype-to-Skype time if you have a remote team), start listing answers to the question: “Event is over and it was a failure. What happened?”</p>
<p>Different people will get different things on the list:</p>
<ul>
<li>We forgot to order vegan food and some of our participants couldn’t eat</li>
<li>Toilets ran out of toilet paper</li>
<li>Everyone arrived 1 minute before the event started and the start was delayed because we couldn’t process them in fast enough</li>
<li>The audio quality with microphones was bad and people in the back couldn’t hear speakers</li>
<li>A keynote speaker cancelled and people were angry because they had bought tickets mainly to see her speak</li>
<li>Somebody sexually harassed another participant</li>
<li>Because of the weather, flights couldn’t land in time and our international guests couldn’t join the event</li>
<li>Our organizers burnt out on the first day</li>
<li>People didn’t read instructions and were mad when things were done differently than usually</li>
</ul>
<blockquote>
<p>“If I had only one hour to save the world, I would spend fifty-five minutes defining the problem, and only five minutes finding the solution.” — Albert Einstein</p>
</blockquote>
<p>You should spend a big portion of your time for this. For a two-hour pre-mortem, first hour should go into coming up with these problems. You’ll thank yourself for your effort later when your event is a huge success.</p>
<h3>Step 2: What are the top 10 problems?</h3>
<p>Next, choose the top 10 problems: the ones with biggest impact but also the ones where the effort to solve is low and impact is rather high. These easy wins can make a huge difference.</p>
<p>To do the selection, you can choose multiple ways but one I recommend and that we use a lot in retrospectives is sticker voting. Everyone gets 10 stickers (all looking the same) and get to vote for their favorite. After that, you calculate points and take ten most voted problems.</p>
<p>There’s often temptation to start talking about the problems and their solutions already in this point but try to steer clear from that. Keep the voting process compact and quick.</p>
<h3>Step 3: Solve problems</h3>
<p>For the next hour of a 2-hour session, spend talking about the top 10 problems. What can you do to proactively avoid the issue? What kind of backup plans can you build to save you if the worst happens? How do you keep your participants satisfied even in case of total disaster?</p>
<p>Make sure you document the action plan and assign it to someone in your team. The more concrete your plan is, more likely you are to succeed. Let’s look at our list above and what we could do:</p>
<ul>
<li>Make and review a plan for food early on, making sure you are aware of participants’ dietary restrictions (if applicable). Do research on different diets and allergies and cater to those in addition to what you might like to eat yourself.</li>
<li>The day before the event or the morning of event, do an inventory of stuff (or make sure the venue staff does) and know where you can get more supplies if needed. Share that to your staff in an easy-to-find place.</li>
<li>Book long enough time for registrations, emphasize early arrival in pre-event communication, and provide something enjoyable for people to do if they arrive early. Make sure you have enough people checking people in.</li>
<li>Do a sound and tech check: make sure sound works, video projector functions, you have backup pdfs of your speakers’ talks in case their laptop doesn’t work with your laptop.</li>
<li>Have a local backup speaker or two. People you can call up with very short time and who are great speakers.</li>
<li>Write a Code of Conduct, have someone responsible for being the contact person and make sure you follow these cases through.</li>
<li>Can you move into a smaller space or delay some of your operations?</li>
<li>Make a plan of how many people you need in your staff. There’s a lot of small things that accumulate to surprising amount of time.</li>
<li>Work on making your communication better and clearer.</li>
</ul>
<p><img src="https://hamatti.org/assets/img/posts/event-is-over/event.jpeg" alt="Photo by Pablo Heimplatz on Unsplash" /></p>
<h3>Step 4: Have an amazing event</h3>
<p>Don’t disregard the items outside your top 10 list. Categorize them, distribute to people in charge and keep them on your desk. When working on a related area, go through the list and figure out how you can make the best out of a tough situation.</p>
<p>And remember, focus on people participating your event (and your team organizing it). Small and large mistakes can be salvaged by listening to your participants, being understanding, saying you’re sorry (and meaning it) and doing your best to save the situation.</p>
<h2>Recap</h2>
<ol>
<li>Book a few hours with your event team</li>
<li>Step into future: “it’s the day after the event and we failed”</li>
<li>List all the things that you can imagine that went wrong</li>
<li>Vote for top 10</li>
<li>Solve those problems before they happen</li>
<li>Rock your event</li>
</ol>
Event organizer - automate what you can, focus on people with all you’ve got
2019-01-31T00:00:00Z
https://hamatti.org/posts/event-organizer-automate-what-you-can-focus-on-people-with-all-youve-got/
<p>I have built hundreds of events, many of them with a very small team. The fewer people you have, the more you have to prioritize your efforts. But I don’t want to compromise communications (I’ve seen what bad comms do to an event) for quality time with people and especially not the other way around. So I have learned to build and automate things. This is a story of how you can do it too.</p>
<p>Organizing events is a lot of work. If you are a professional event organizer who’s only job is to do events, you probably already have invested in learning tools that can make your life easier. If you are working in marketing or development, and event organizing is just a tangent of your job, I have collected a few ideas to help you focus on what truly matters: your participants.</p>
<p><img src="https://hamatti.org/assets/img/posts/event-organizer-automate/festival.jpeg" alt="" /></p>
<h2>Why automate?</h2>
<p>If you really want to build a good event — more than just putting up Facebook event and waiting for people to arrive (free tip: doesn’t work)— there are many options to keep your participants engaged and build community and continuity even after the event.</p>
<p>That requires a lot of administrative work. I believe most of us agree that it’s not really that valuable nor interesting in itself: sitting at your desk filling excel, exporting and importing CSV files and sending emails you could automate.</p>
<p>I’m a big fan of Zapier. It allows you to build action pipelines from the data you gather. It’s a service that every marketing person, entrepreneur, and event organizer should have in their toolbox. No need to write code (sometimes it helps though). But it’s still only a tool and it you need to build your event strategy before you start building automation.</p>
<p><img src="https://hamatti.org/assets/img/posts/event-organizer-automate/robot.jpeg" alt="" /></p>
<h2>Strategy</h2>
<p>There is no one truth when it comes to events, communities and communication. But one of the things I have learned is that many event organizers horribly neglect communication, just trusting that if they build it, people will come.</p>
<h3>1. What information do you need?</h3>
<p>Planning ahead what information you need, is not only a great thing to do to look professional, it’s also important in the age of GDPR. Figuring out you forgot to ask the dietary restrictions when you’re ordering food creates extra hassle, delays and can seem to participants like you didn’t think things through.</p>
<p>I once organized a 3-month program, and forgot to ask participants email address in the registration form. I can tell you it was super embarrassing trying to find people without any contact information. Luckily, everything worked out in the end and I have learned from that mistake.</p>
<h3>2. What’s your plan for pre, during and post event communication?</h3>
<p>Your methods, content and frequency of your communication depends on what kind of an event you are building. For a single one-off meetup or breakfast event, you probably want to do launch, one pre-event content email, last minute reminder and a thank you email with next steps and material shown in the event. For a larger program like a 5-session workshop series or 3-month program, you’re gonna need much more.</p>
<p>You should plan the schedule and the content skeleton for those emails in advance. With modern tools like Mailchimp or Eventbrite, you can build a scheduled collection of emails to be sent when people join the list, N days before the event, the day after the event, and so on. This way you don’t have to worry about remembering to send an email the next day — when there’s often a need for well-deserved resting after hard work leading to the event.</p>
<h3>3. Social media during the event</h3>
<p>For larger events, you’ll probably have a dedicated social media person but for smaller ones, you might not. In those cases, pre-scheduling tweets can help you build content people can react to and interact with without you having to stare your phone all the time.</p>
<p>Tools like Buffer or Hootsuite are great for scheduling posts and managing your social media, allowing you to focus on people.</p>
<p><img src="https://hamatti.org/assets/img/posts/event-organizer-automate/cords.jpeg" alt="" /></p>
<h2>Putting it all together</h2>
<p>World is full of these tools for all sorts of occasions. The magic happens when you start putting them into work together.</p>
<p>What if I told you that you can build an automated event sequence with a just few hours of work?</p>
<p>You can setup a system where a participant registers to your event in Eventbrite or through a Google Form. Then this gets added to an email list which triggers automatic welcome email. Not only that it continue with a collection of pre-scheduled emails up to and after the event. The registrant gets a calendar invite and also invited to Slack community.</p>
<p>Once the registration is closed, the system can automatically send a list of dietary restrictions to the person responsible for ordering food. The same can be done with t-shirt sizes and other things. At the end the participants information gets added to the company CRM for follow-ups.</p>
<p><strong>Superstar bonus technique</strong>: setup automatic tweets to be sent when certain thresholds of participants registered is reached.</p>
<p>And don’t forget gathering feedback. Automated emails with feedback forms sent after the event and best feedback tweeted and added to your Wordpress-run website.</p>
<p>And all of these, without you having to do anything after the initial setup. Freeing your time to answer people’s questions, interacting with members, taking care of your speakers, and what ever you need that truly makes an impact.</p>
<p><strong>Does it sound too good to be true?</strong> I think it does. But it’s all possible. Let’s learn the basics and after that, you can start imagining what else you can build.</p>
<h3>Setup</h3>
<p>Let’s create an event in Eventbrite. Their system is very easy to use and their onboarding process makes it easy for you to figure it out. Then we create a Google Sheet with the first row filled with headers (this is important for future integrations) matching the content we’ll collect through Eventbrite.</p>
<p>At this point, you should have or you should plan for the email communications. Let’s build a list for this event in Mailchimp. Then we setup our welcome email, pre-marketing and post event email template.</p>
<p><img src="https://hamatti.org/assets/img/posts/event-organizer-automate/zapier.png" alt="" /></p>
<h3>Making things happen</h3>
<p>Now let’s start putting pieces together. Using Zapier, you can connect two apps: like Eventbrite and Mailchimp so that when someone registers for the event, they are automatically added to our mailing list (and then Mailchimp’s own automation will kick in and build the pre-event communication).</p>
<p>We also want to save Eventbrite data to Google Sheet because I find that easiest place to build integrations from. We can use to collect data for dietary restrictions and t-shirts to send as an email to anyone responsible for those.</p>
<p>With Zapier, I feel like the opportunities are limitless. They have some templates for Zaps like the ones linked above but the true power comes from building your own, multi-step Zaps. If you can code even a little or understand enough to be able to Google, copy-paste and modify existing code examples, the opportunities become even wider.</p>
<p>For scheduling social media like tweets, you don’t necessary even need Zapier (although, it can also be used to make things happen) but you can do scheduling directly through Buffer.</p>
<h3>Focus on what’s important</h3>
<p>For the past few years, data-driven has been a mantra in the business world. And while I agree that for many things, it’s a great approach, I believe when it comes to events and community, taking an empathy-driven approach is much better.</p>
<p>You should focus your time on people interactions, not filling spreadsheets and writing mass emails. For the first event, it might take a while to get all the apps setup and learn how to use them. But after you have built your base, you’re gonna thank yourself.</p>
<blockquote>
<p>“I’ve learned that people will forget what you said, people will forget what you did, but people will never forget how you made them feel.” -Maya Angelou</p>
</blockquote>
Better bash history search with McFly
2019-01-28T00:00:00Z
https://hamatti.org/posts/better-bash-history-with-mcfly/
<p>If there's one functionality of bash that most beginners don't know but get very excited about when they discover it, it's history. First you learn to go through the history by pressing up and down, then you find out that <code>CTRL+R</code> is the spell that gives you access to write commands and find them from history.</p>
<p>I'm a huge fan of that. Probably 80-90% of my bash command history is repeating commands over and over again. Whether it's <code>npm install</code>, <code>npm run start</code>, <code>git add .</code> or something similar, I can find it from my history.</p>
<p>But the built-in search isn't always the optimal one. You can't really <strong>see</strong> the history: you have to know what you're looking for. Luckily, developer community is amazing and <a href="https://github.com/cantino">Andrew Cantino</a> has built a tool to augment your bash history.</p>
<p>Meet <a href="https://github.com/cantino/mcfly">McFly</a>. Great Scott!</p>
<p><img src="https://hamatti.org/assets/img/posts/mcfly/bash.gif" alt="Screenrecording of bash shell using mcfly to read through history, re-run commands and edit commands" /></p>
<p>Instead of being faced with blank black prompt, you get nice visual view of your history. You can search by writing a part of the command, you can navigate with arrow keys and either run (ENTER) or edit (TAB) your commands.</p>
<p>It also features a smart system that takes into account certain things to be more relevant: in which directory you are, what's the context of the command, if the command has failed or not, etc.</p>
<p>In the beginning, mcfly felt a little bit slower because it takes a moment to start up. But after using it a while, I don't want to go back to guessing game.<br />
Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>
Post a message from Slack to Twitter with Zapier
2019-01-27T00:00:00Z
https://hamatti.org/posts/slack-to-twitter-with-zapier/
<p>I was chatting with friend on a Slack community last Friday, and he had a feature he'd love to have. When people post new job ads into #jobs channel, he'd love to automatically post them to Twitter for larger reach. I liked the idea!</p>
<p>As a big fan of automation and <a href="https://zapier.com/">Zapier</a>, I told I could take a look over the weekend and so I did.</p>
<h2>Purely Zapier-run option</h2>
<p>If you are not familiar with Zapier, it's an app that allows you to connect web apps to each other. You specify a trigger (new post in Slack, new tweet, new signup for email list, new form submission, you name it), any intermediate steps (filtering, text transformation, logging to Google Sheets, etc) and finally an action like posting to Twitter.</p>
<p>After bit of testing things out, here's a Zap I created for posting wanted job ads to Twitter.</p>
<h3>1. Trigger from Slack</h3>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/4uvsoz466p8t9lnt0x24.png" alt="Screenshot from Zapier showing Slack selected as a Trigger and action New Message Posted to Channel selected" /></p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/yf74fmpgdrqk07cgszcd.png" alt="Screenshot from Zapier showing Jobs channel selected as a trigger channel and bot option selected as No" /></p>
<p>Select Slack as your source and in <em>Edit options</em> section selected the wanted channel and if you want to trigger with bots or only humans.</p>
<h3>2. Filter by content</h3>
<p>I only wanted to include some posts with a certain hashtag (for example, #twittershare). This way users could decide if they want their post to be tweeted and we avoid awkward moments where someone answers a question and ends up in Twitter.</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/m4ftyt56qnlj9dsn5y0u.png" alt="Screenshot from Zapier showing Filter action selected in app selection screen" /></p>
<p>Use <em>Filter</em> app by Zapier to filter by values.</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/94lze3xsiubcqglgu6zf.png" alt="Screenshot from Zapier showing Only continue if... option selected for Filter" /></p>
<p>We want to only continue if the message from Slack matches our filter.</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/v4iqbe475na0im14ietk.png" alt="Screenshot from Zapier showing we are using Text Contains #twittershare filtering" /></p>
<p>Filtering by text is easy.</p>
<h3>3. Remove #twittershare from message</h3>
<p>We don't actually want to show our keyword in the tweet, it's only for managing the Zap. So let's remove it by using <em>Formatter</em> action:</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/g3xvp5alefg3qsaqknmi.png" alt="Screenshot from Zapier showing we have selected Formatter app" /></p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/c3kovy9kwmg26qgj17de.png" alt="Screenshot from Zapier showing we have selected text formatter" /></p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/mdpm330i1c9597hhr1hs.png" alt="Screenshot from Zapier showing how to remove text with replace function" /></p>
<h3>4. Let's tweet!</h3>
<p>Posting a tweet with Zapier is easy as pie.</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/ui8i65dv54sf6klccda0.png" alt="Screenshot from Zapier showing we have selected Twitter app" /></p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/b782awpizej8srctdyty.png" alt="Screenshot from Zapier showing we have selected the Create tweet action" /></p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/sne24u7n9nnhip3j49xr.png" alt="Screenshot from Zapier showing how to use templates to build a tweet" /></p>
<p>With Twitter app, you can use templating to build a message. You can add text, use values from Slack post (like timestamp, poster name, text, etc).</p>
<h3>Conclusion</h3>
<p>After you have done these steps, every time someone posts a message with keyword #twittershare in the message to #jobs channel, our account will send a tweet to Twitter.</p>
<p>This is this easiest way to make things happen and I highly recommend using this.</p>
<h2>Slack-GoogleSheets-Twitter option</h2>
<p>Zapier's free tier only allows for two-step Zaps to be created. So you cannot filter, you cannot replace text, etc. The cheapest paid tier is $25/month and if you only need one Zap like the one described above, you might be hesitant on paying.</p>
<p>Good thing is that we can basically chain Zaps by creating input to one service in one Zap and reading from it in another Zap. Add some Google Script programming and we can build ours in two Zaps and 23 lines of Javascript.</p>
<p>By reading through steps in our Zapier-only option, you can see how to select apps and go through setup so I will only show the important sections from this point on.</p>
<h3>Zap 1: Slack to Google Sheets</h3>
<h4>1. Trigger from Slack</h4>
<p>This option is exactly the same as above, so I won't repeat it here.</p>
<h4>2. Create Google Sheet</h4>
<p>Log into your Google Drive and create a new Spreadsheet. I named mine Jobs for Zapier connection. Create two sheets inside it, named "source" and "target". For both of these, add headers "Timestamp", "User" and "Content" into cells A1 through C1. We are only logging timestamp and user for debugging purposes.</p>
<h4>3. Write to source sheet</h4>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/pxhxa2j6xuz966cul14o.png" alt="Screenshot from Zapier showing choosing action Create Spreadsheet Row" /></p>
<p>Use Create Spreadsheet Row as your action.</p>
<p><img src="https://thepracticaldev.s3.amazonaws.com/i/kv2bo9w6x0byebd23mel.png" alt="Screenshot from Zapier showing how to add data" /></p>
<p>Choose your spreadsheet and source sheet inside it. Choose which values to write to columns and you're good to go.</p>
<p>After this, we'll have a Google Spreadsheet that automatically adds every post to Slack #jobs channel into a source sheet. Next, we need to do little bit of programming in Google Scripts to duplicate only the ones we want from source into target.</p>
<h3>Google Script</h3>
<p>In Google Spreadsheet, go to menu <strong>Tools->Script editor</strong>. This will open a new script editor, you might have to give it permission to your Google Drive.</p>
<p>Give your script a name, I called mine <strong>Conditional Copy</strong>. Write the following code into the <a href="http://code.gs/">Code.gs</a> file:</p>
<pre class="language-javascript"><code class="language-javascript">
function conditionalNewRow() {
var SPREADSHEET_ID = ""; // This one you can get from the url of your spreadsheet.
var TIMESTAMP = 0;
var USER = 1;
var CONTENT = 2;
var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
var sourceSheet = ss.getSheetByName("source");
var targetSheet = ss.getSheetByName("target");
var lastRowIdx = sourceSheet.getLastRow();
var range = sourceSheet.getRange(2, 1, lastRowIdx - 1, 3);
var lastRow = range.getValues().splice(-1)[0];
var newContent = lastRow[CONTENT];
if (newContent.indexOf("#twittershare") > -1) {
var stripped = newContent.replace("#twittershare", "");
var newRow = [lastRow[TIMESTAMP], lastRow[USER], stripped];
targetSheet.appendRow(newRow);
}
}</code></pre>
<p>After you have saved the file, go to menu <strong>Edit->Current project's triggers</strong>. Select <strong>Add trigger</strong>. You need to choose your function <code>conditionalNewRow</code> and event type <strong>On change</strong>. This will cause the function to be run every time your <code>source</code> sheet updates through Zapier.</p>
<p>You can test it to function by posting a new post to your Slack channel with and without the keyword and see that both of them should appear in source sheet and only the one with keyword should appear in target. If that works correctly, continue to the next step. If it fails, make sure your Zap 1 is switched on.</p>
<p>Please note that this function happens on every change and always reads the last line from source. If you do any manual changes to the file, it might re-send your latest tweet.</p>
<h3>Zap 2: Sheets to Twitter</h3>
<p>This is similar to Slack to Twitter from our Zapier-only route. Select <strong>Google Sheets</strong> as an app for trigger and <strong>New Spreadsheet Row</strong> an action. Choose target as your sheet this time.</p>
<p>Twitter side of the action is same as in Zapier-only route.</p>
<h3>Tada, you're done!</h3>
<p>It took a few extra steps, bit of programming and added a bunch of variables that can cause this to fail. For my testing, it seemed to work really nicely and I got in tweets just as I liked them. And it works on a free-tier with Zapier.</p>
Learning programming has never been more accessible in Helsinki
2019-01-17T00:00:00Z
https://hamatti.org/posts/learning-programming-has-never-been-more-accessible-in-Helsinki/
<p>If you're interested in learning programming, the scene in Helsinki is blooming right now. There are long-term options as well as weekend workshops, evening workshops and support groups so everyone should be able to find their place in the community. And if you're an experienced developer who wants to share their knowledge, most of the community initiatives are always happy to have new people join as coaches.</p>
<p>This list is not exhaustive – these are just the ones I'm familiar with and new ones pop up all the time. If I'm missing something, please let me know in the comments!</p>
<h2>Long-term opportunities</h2>
<p>Universities are having their traditional degree programs in computer science and software engineering if you're in pursuit of a degree. In addition to full degrees, <a href="https://mooc.fi/">University of Helsinki also runs MOOCs</a> (Massive Open Online Course) that are available to everyone at their leisure. Whether you want to learn <a href="https://ohjelmointi-19.mooc.fi/">basics of programming</a>, <a href="https://tietokantojen-perusteet-19.mooc.fi/">understand databases</a>, or <a href="https://www.elementsofai.com/fi">dive into the world of AI</a>, the opportunities are there. Unfortunately some of them are only available in Finnish. <a href="https://www.facebook.com/kovimmatkoodaa/">Kovimmat Koodaa</a> is also organizing support group meetings for people working on the programming MOOC course so you don't have to fight the problems alone.</p>
<p><a href="https://www.hive.fi/en/">Hive Helsinki</a> is the newest kid on the block. A tuition-free 3-year program for learning software development has the application period open right now. Based on peer-to-peer and project-based learning, the aim is to provide great set of skills for anyone wanting to enter the software industry.</p>
<h2>Workshops</h2>
<p><a href="https://codebar.io/">codebar Helsinki</a> is a non-profit initiative that facilitates the growth of a diverse tech community by running regular programming workshops. We have workshops monthly, and in a 3-hour workshop you'll get personal coaching in groups of 2 or 3 people per coach and the format is very flexible. Instead of a set curriculum, there are bunch of tutorials on the website and based on the availability of coaches, the choice of the language to learn is nearly limitless. I've been coaching with codebar since last June when Anastasia and James started hosting the events in Helsinki and I absolutely love it.</p>
<p><a href="https://theshortcut.org/">The Shortcut</a> organizes lots of weekly opportunities for learning programming. In the spring of 2019, there's <a href="https://www.facebook.com/events/275686103122964/">Javascript Club every Tuesday</a>, <a href="https://www.facebook.com/events/328978091050152/">Girls' Coding Club on Wednesdays</a> and <a href="https://www.facebook.com/events/2113382432075883/">React Coding Club on Thursdays</a>. They also have a <a href="https://theshortcut.org/python-training-programmes-2019">Python 0 to 1 program</a> (with applications closing on Jan 18th 2019) — a 5-week program for learning programming with Python. In addition to their programming clubs, they also bring together a very international and lovely community.</p>
<p><a href="http://railsgirls.com/">Rails Girls</a> and <a href="https://djangogirls.org/">Django Girls</a> are weekend-long workshops with events hosted occasionally in Helsinki. During the workshops participants will learn how to build a modern web application with the help of tutorials and coaches in small groups of 4-5. Keep an eye on their websites for upcoming events!</p>
<p>Last year Helsinki saw incredible amount of programming workshops as part of <a href="https://ohjelmistoebusiness.fi/mimmitkoodaa/">the Mimmit Koodaa initiative</a>. Hosted by local companies people had opportunity to learn anything from React to Web Components, and Drupal to Django. Keep an eye for upcoming workshops also in the spring of 2019!</p>
<p><a href="https://www.freecodecamp.org/">FreeCodeCamp.org</a> is a wonderful online platform for learning programming and there are <a href="https://www.facebook.com/pg/tikishabudin/events/">weekly gatherings for students in Helsinki area</a> organized by Tiki Shabudin.</p>
<h2>Meetups</h2>
<p>Another great resource, albeit a bit more indirect, is the meetup scene. In Helsinki there are lots of events organized by developers for developers and anyone regardless of their skill level is welcome. In addition to hearing stories about projects people have built or introductions to new technologies, meetups are a great way to network with the local community.</p>
<p><a href="http://meetup.com/">Meetup.com</a> has <a href="https://www.meetup.com/cities/fi/helsinki/tech/">a great catalogue of meetup groups nearby</a> and another resource to find meetups is <a href="https://meetabit.com/">Meetabit.com</a>. There are meetups for almost every technology or sub group and the community is very welcoming to newcomers, even if sometimes some of the talks are more technical.</p>
<hr />
<p>All in all ,the scene for meeting fellow developers and learning programming is wide and diverse. Whether you're interested in understanding how software are built or looking to build a career in software development, you can find something to do every week to support your goals. Be aware though: the demand is still way beyond the supply so most events with limited seats fill up in hours, if not minutes.</p>
Year in Review 2018
2018-12-29T00:00:00Z
https://hamatti.org/posts/year-in-review-2018/
<p>
I love reflecting and looking back to the past year: to celebrate
accomplishments, to acknowledge failures and to recognize the progress. This
year was full of different things, directions and feelings and I have spent a
lot of time during December on journaling, collecting, summarizing and trying
to figure out, what really happened.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/1_HEZ_K3tPp-9sLv6dwNiFBA.jpeg" class="kg-image" alt="Me standing next to a screen giving a presentation" />
</figure>
<h2 id="workshops-talks-and-events">Workshops, talks and events</h2>
<p>
One of the biggest improvements in my professional life was taken during this
year in the public speaking and workshop hosting section. While I had done ~20
talks/workshops a year past couple of years, this year I not only more than
doubled that, I also started getting invited to do it more and more. For me,
there are couple of stages of success in that aspect:
</p>
<ol>
<li>You are accepted to talk or give a workshop when you suggest it.</li>
<li>You are invited back to give the same workshop again.</li>
<li>
You are invited to give a talk/workshop again in a different
place/organization.
</li>
</ol>
<p>
I had done #1 quite a lot lately but this year was really a breakthrough for
#2 and #3. And given that my calendar for February and March 2019 is filling
up fast with new invites to give talks and workshops, I couldn’t be happier on
that end.
</p>
<p>My main highlights for talks and workshops for this year:</p>
<ol>
<li>
<a href="http://hamatti.org/perjantaipresis" rel="noopener nofollow">Communities in Marketing @ Fraktio’s Perjantaipresis</a><br />This was my first proper talk about community building to a public
audience. I talked about different ways companies can build communities and
how they bring value to the company. The talk was streamed and the video
recording, slides and a recap blog post can be found from the link above.
</li>
<li>
<a href="https://www.slideshare.net/JuhaMattiSantala/oulues-human-accelerator-dream-workshop" rel="noopener nofollow">Dream Workshop @ Boost Turku Human Acccelerator & OuluES Human
Accelerator</a><br />This year I had the opportunity to give a workshop in two Human
Accelerator programs in Turku and Oulu. It’s a workshop I took part in 2013
that propelled my life into a whole new trajectory and I wanted to spread
that to new people. Being invited back to both of them for 2019 was the
biggest <em><em>thank you</em></em> I could imagine.
</li>
<li>
<a href="https://youtu.be/iOq6VR8UYzQ?t=5723" rel="noopener nofollow">Inspiration, Learning and Experimentation in Codepen @ HelsinkiJS &
Turku ❤️ Frontend</a><br />I gave a tech talk about Codepen in two meetups this year. It
explores the wonderful tool and community platform Codepen and how it
inspires me to do better frontend work, helps me learn new techniques and
serves as a testing playground.
</li>
</ol>
<p>
In addition to these three, I talked about Vim, debugging, MVPs, Python 3,
importance of company culture, how to be a kickass event volunteer, service
design, time management, open source, job hunting, and bunch of other stuff
close to my heart. I taught programming to few dozen new people in mentoring
program and multiple workshops. And I helped bunch of early stage startups in
LaureaES’s Cambridge Venture Camp, LaureaES’s Hatch incubator and Boost
Turku’s Startup Journey.
</p>
<p>
Bringing together people to learn and be inspired from each other is a passion
close to my heart. Turku ❤️ Frontend finished its third year and I was able to
pass the torch to new people after moving away from Turku. During the spring,
we hosted five great meetups, a brewery tour to
<a href="http://www.radbrew.net/" rel="noopener nofollow">Radbrew</a> and
organized a successful
<a href="https://medium.com/@Hamatti/how-i-convinced-15-companies-to-contribute-to-oss-e553c8137f36?source=user_profile---------14------------------" rel="noopener">Turku Gives Back</a>
campaign to promote open source.
</p>
<p>
All together, 2018 saw me organizing, coaching, teaching, speaking or doing a
workshop in almost 50 events in addition to the work related projects.
</p>
<h2 id="letting-go">Letting go</h2>
<p>
Another big theme of the year, totally unplanned, was letting go. I moved on
from multiple organizations and projects to give the opportunity to new people
to grow, and to free up time on my calendar for new projects.
</p>
<p>
In May, I wrapped up 15 years of table hockey playing and organizing. Over the
15 years, I was involved in starting two clubs (Paimion Keikkoilijat and Turun
Seudun Pöytäjääkiekko), was part of the Finnish Table Hockey Association board
as well as serving 4 years as a webmaster building website, and tools for
tournament organizers, working on social media and community, organizing way
over hundred tournaments on local, national and international level, including
2011 World Championships.
</p>
<p>
After moving to Helsinki to work at Futurice, I also passed on the torch for
Teemu and Matias to keep on building
<a href="https://turkufrontend.fi/" rel="noopener nofollow">Turku ❤️ Frontend</a>. It was a painful decision and made me personally feel like a failure but I
was glad to see the community was so much more than just me and I had the
pleasure to return as a speaker in December meetup.
</p>
<p>
I also pulled down the curtains for
<a href="http://koodimentori.fi/" rel="noopener nofollow">Koodimentori</a>
after a nice one year sprint. I loved working with Iita, Antti and Kiia and I
learned a bunch of stuff, deciding to start a new one with a bit different
twist.
</p>
<p>
That new one with a twist become
<a href="http://codeparrot.io/" rel="noopener nofollow">Code Parrot</a> — a
6-month program for junior developers helping them to become better, to learn
both technical and communication skills, to build a portfolio project, and to
network with the top industry people in the country. Unfortunately, I had to
put it on hold when a burnout started to close in on me during the end of
summer. Hopefully I can launch Code Parrot in 2019.
</p>
<p>
During the summer of 2017 we also started working on a frontend tech
conference in Turku: I brought together a great team, we built plans, worked
with amazing partners to build the visual brand, book the venue and
everything. Unfortunately, my ambitious plans regarding speakers came tumbling
down as one after another I got the No answer from everyone but one. Deciding
to shut that operation down was also very painful.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/1_ypcBV0IKPasPiMvTb43_Mw.jpeg" class="kg-image" alt="A selfie in staircase of me wearing a red cap and a backpack" />
</figure>
<h2 id="minimalism">Minimalism</h2>
<p>
I continued my minimalistic lifestyle and put it to a test. Between April and
August, I spent five months living with just my backpack: no home, only having
stuff I could carry with me. Luckily, I have plenty of friends who travel a
lot which allowed me to find temporary places to stay.
</p>
<p>
The ease of living surprised me and the feeling of freedom was amazing. Being
able to move to a new place by packing everything up in 30 minutes and taking
a subway to work and walking to a new place after working hours with just a
backpack was incredible. Settling down in an apartment in September felt bit
difficult (and the stuff started to appear in every nook and corner) but four
months later I’m still managing.
</p>
<p>
My city-bound digital nomadism experiment reminded me how little we actually
need to live a meaningful and full life. And it was a wonderful test to prove
myself that I can do a nomadic lifestyle — a dream of mine for the following
years.
</p>
<h2 id="work-and-life">Work and life</h2>
<p>
This year started with a continuation of unemployment from 2017. Even though I
eventually found a job, the entire year was plagued with the feeling of lack
of direction. I spent entire year trying to figure myself out and even as the
year is turning to its very end, it feels like I haven’t figured anything out.
</p>
<p>
I had lots of job interviews and recruitment processes during the first three
months of the year. Lots of ice cream and tears. Finally in April I joined
<a href="https://futurice.com/" rel="noopener nofollow">Futurice</a> as a
full-stack web developer and worked on a couple of client projects with
additional student collaboration, open source promotion, event organizing and
promotion work. As I start the 2019 with a new client and new project, I’m
still trying to find myself in this world.
</p>
<p>
Trying to find myself and the best way to have an impact in world, I keep
experimenting and exploring. In October, I started streaming open source
software development in
<a href="https://twitch.tv/hamatti" rel="noopener nofollow">Twitch</a>. I
found it very enjoyable (to my surprise) but with all other things in life
taking up a lot of time I had to skip more streams in my schedule than I had
hoped. But even with the few I did, I ended up in having very interesting
discussions with people I had never met but who recognized my in real life
from Twitch. For 2019, I’m hoping to focus on more formatted educational
content in Twitch.
</p>
<h2 id="movies-games-and-books">Movies, games and books</h2>
<p>
On a lighter aspect of life, I enjoyed a lot of entertainment.
<em><em>Mamma Mia 2: Here We Go Again</em></em> was the best movie of the year
with <em><em>Avengers: Infinity War</em></em> and
<em><em>Ready Player One</em></em> being very good contenders. Unfortunately,
I haven’t seen the new<em><em> Fantastic Beasts</em></em> nor the new
<em><em>Spiderman</em></em> movie yet. I also retried to love
<em><em>La La Land</em></em> but even with all the pieces being in place, I
just feel nothing when watching the movie.
</p>
<p>
For books, I ended up re-reading a lot of books I had read in 2017. Cal
Newport’s <em><em>So Good They Can’t Ignore You</em></em>, Mark Manson’s <em><em>Subtle Art of Not Giving a Fuck</em></em>, and Dale Carnegie’s
<em><em>How to Win Friends and Influence People</em></em> gave me new
perspectives to things. Jacqueline Jensen’s
<em><em>Travel Isn’t the Answer</em></em> resonated with my thoughts on
travel, minimalism and intentional living.
</p>
<p>
<em><em>Celeste</em></em> had no competition in the best video game of the
year category. I absolutely loved it.
<em><em>Red Dead Redemption 2</em></em> made sure my Christmas holiday wasn’t
spent on building new side projects and I continued playing
<em><em>FIFA, NHL and NBA</em></em> series as my go-to games — for like 25th
year in the row. For bit more experimental games, I enjoyed
<em><em>Her Story</em></em>. And I replayed <em><em>Firewatch</em></em> which is one of my absolute
favorite games of all time. Yes, I like it even more than
<em><em>Zelda games</em></em>.
</p>
<h2 id="friends">Friends</h2>
<p>
One of the things I love in my life is that I get to meet and get to know so
many amazing people from all around the world. The Buffer’s Slack community
has grown very important group of people for me, the developer and teaching
communities likewise. Starting a new job brought me lots of new friends (and
some puppies to scratch) and during the Slush I got to enjoy the most
welcoming presence of Allison, Sarah, Brians, Amy and others from the
Microsoft Azure Cloud Advocate team for two days.
</p>
<p>
As many of my friends live around the world, I don’t get to see them always
daily or weekly. This year I had the opportunity to spend time with some
people I hadn’t seen in a year and I couldn’t be happier about that. With some
people I wish I would have had the possibility to spend more time but hearts
are fragile and break sometimes, so this year was no exception to that either.
</p>
<h1 id="2019-adventure">2019 == Adventure</h1>
<p>
Wrapping up a year with a month of reflection, journaling and writing blog
posts is wonderful. It gives you an option to mentally finish things that are
still on your mind which gives you space for existing things to grow and new
things to appear. My next year will be an adventure.
</p>
<p>
I have submitted a few conference talk proposals for European tech conferences
and have a few more lined up waiting for me to submit my talks. I want to
explore central Europe, and to grow both as a human being but also as a
professional.
</p>
<p>
By the end of 2019,
<strong><strong>I will be working in a full-time developer advocate / tech community
manager type of role</strong></strong>
helping developers become better, inspire each other and learn from each
other.
</p>
<p>
I will <strong><strong>launch Code Parrot</strong></strong> to help junior
developers become better.
</p>
<p>
And I will
<strong><strong>talk in at least one international conference</strong></strong>
outside Finland.
</p>
<p>
<strong><strong>Because 2019 is our year. Let’s make it the best possible year.</strong></strong>
</p>
<p>
If you’re looking to do some end-of-year reflections but don’t know what to
think about, check out
<a href="https://yearcompass.com/" rel="noopener nofollow">Year Compass</a>.
This was my fourth year doing it and I absolutely love it.
</p>
Internet = People = ❤️
2018-11-14T00:00:00Z
https://hamatti.org/posts/internet-people-love/
<p>I love Internet. And I have ever since I gained access to it in my pre-teens at the turn of the millennium. There has been so many different steps in my path through the Internet forest but almost all of them are about people.</p>
<h2>The Early Days: In Internet Nobody Knows You’re a Dog</h2>
<p>These days with Facebooks and the boom of the video, we have an Internet where many people represent themselves with their real name and/or face. But back in the day, it was forums and IRC where you really didn’t know who people were unless you got to know them.</p>
<p>And I loved it. As a socially awkward kiddo from a small town in a small country, it was a bliss. I was able to be whatever I wanted: I was evaluated based on my thoughts and my writing. Nobody knew if you’re a guy or a girl (or a dog), short or tall, socially awkward or charismatic, or 12 or 30 years old.</p>
<p>Even though you should never meet strangers you’ve encountered online, I did exactly the opposite. I traveled across the country to meet people I had only interacted with online — some of them I had never talked with over the phone or even seen a picture.</p>
<p>Some people I knew as a nickname-only basis for years before we actually met in the real life. Meeting those people and continuing the discussion like two old friends was such an interesting experience when at the same time you realize it’s the first time you’re meeting.</p>
<h2>The Modern Days: Real People with Real Faces</h2>
<p>Then the world changed. The transformation from anonymity and nicknames changed to real names and text-only chats were enhanced with pictures and videos and more real-life connection.</p>
<p>For me, it was a rough and difficult change. Suddenly, it wasn’t anymore just my thoughts but the scene started to change. Video and podcasts started to get more attention and a lot of community started to form around those. I felt bad. I felt that my time had gone. But it hadn’t, it was just starting out.</p>
<p><img src="https://hamatti.org/assets/img/posts/internet-people-love/community-coffee.png" alt="Buffer Community Coffee :: https://twitter.com/ariellemargot/status/1062751383319760896" /></p>
<p>I’ve been involved in a lot of online communities. Dozens and dozens of them. I have made friends who I’ve been friends with for almost two decades. Today I was in a video call with bunch of amazing people from all around the world: the US, the UK, Cayman Islands, India, Finland, Romania. <strong>And that’s the beauty of the Internet</strong>.</p>
<p>While Internet is no longer anonymous, it still connects people across the planet in a way nothing else does. And it enables friends to move around the world and still be in day-to-day face-to-face touch. Or you can make new friends from across the planet, talk with them and maybe one day we can meet in person.</p>
<p>For me, these communities provide so much emotional support, laughter, people to talk about movies and video games and communities and programming and everything in the world with. Through them, I’ve gained so much confidence and practice that these days jumping on a group video call with bunch of strangers is not something I stress over for days — it’s rather something I look forward to every day.</p>
Developers do design — but we’re not taught it
2018-07-27T00:00:00Z
https://hamatti.org/posts/developers-do-design-but-were-not-taught-it/
<p>In many companies, most of the days we developers end up doing design choices. Whether it’s graphic or service design, by the virtue of building things we make design decisions. But most developers are not taught design, not even the very basics.</p>
<h2>Everyday design decisions</h2>
<p>Not all of us have a privilege of working with amazing designers for every feature we build but nothing is done without a design. Maybe someone gave you a sketch about a feature with a single state and you end up making decisions on what the other states look like — you make a design decision. Or maybe you’re given a spec of what needs to be done but not how — you make a design decision.</p>
<p>Most recently, I got a feature request for a data visualization. It contained a screenshot from Excel giving me a basic idea of what they were looking for. At that point, my brain should have lit up and said, wait a second, how about X, Y and Z. How about the empty state? What if this category of data doesn’t exist? How are these calculations affected if there are null values?</p>
<p>As the person requesting it ended up being on a holiday the moment I realized these things, I ended up creating the initial draft. I know the weight these initial decisions have: unless it’s horrible, it will most likely pass. But I ended up making an ad-hoc design decision.</p>
<h2>Nobody’s teaching that</h2>
<p>And these things happen every day. There are lots of ways to implement certain features and if we don’t think about the design: user experience, workflow, etc, we’ll end up making bad decisions.</p>
<p>The problem is, nobody is teaching us this stuff at school*. Or even telling us that these things exist. I often hear people (devs and non-devs) refer to many of these designs as engineer-designed. Basically meaning that it functions as it should but without the touch of a designer. Function over form and usability.</p>
<p>We were taught software design but almost exclusively from technical point of view. We drew UML and ER diagrams and thought about how different parts of the software should interact with each other. But not with people.</p>
<p>I’m glad that I spent two years organizing workshops on design for startup entrepreneurs. I learned so much about usability, service design, user experience, business goals and all the other factors that we should think about when we implement new features or even fix bugs.</p>
<p>That has made me a much better developer and I spend more time outside my code editor and with the users, other stakeholders and my notebook.</p>
<p><em>This is based on my experience of the Finnish universities. Your mileage might vary. If your school teaches design for developers as mandatory studies, please let me know, that sounds awesome.</em></p>
City Bound Nomadic
2018-07-06T00:00:00Z
https://hamatti.org/posts/city-bound-nomadic/
<p>In the near future (say, within the next 5 years), I want to explore the possibilities of a digital nomad lifestyle. Working independently of location and time is a compelling idea and having followed digital nomads in blogs and Youtube and Instagram for quite a while, I'd definitely love to give it a try. Seeing the world, learning about new cultures and being able to experience the wonders that the world has to offer — sounds good, right?</p>
<p>I realized some time ago that it takes quite a lot to jump from regular 9-to-5 office job and permanent home to remote-only, timezone varying work with no permanent place to call home. So I decided to start learning the required skills and getting used to things before taking a full leap. I also felt that I needed to hone my skills so I could find enough work to keep me going once I decide to start traveling.</p>
<h3>"No home" lifestyle</h3>
<p>I've been "homeless" since the beginning of April. I've been living on and off from different places as my friends or friends' friends have been travelling. A month here, two months there, 3 weeks somewhere else. I have everything I need in my backpack and I explore different neighbourhoods of the city. It's very freeing when moving to a new apartment takes 30 minutes of packing and however long it takes to take the subway or the bus to a new home.</p>
<p>As I'm writing this on a Monday afternoon, I'm right in the middle of a move. I picked up my stuff in the morning, closing the door to an apartment that served me home for 10 days. I sit in a restaurant next to our office and in the evening, I'll close my eyes in a new apartment that will serve as my home for the next month.</p>
<h3>Remote work</h3>
<p>I worked for over 2 years without a desk. Currently I'm spending most of my days at client's office as I work as a software consultant but I try to work remotely as much as possible and I'm hoping to find projects that would allow me to go as close to 100% remote as possible.</p>
<p>I love remote. It puts the focus on the outcome of your work instead of the input and you can choose where, when and how much to socialize. Sure, lacking colleagues who work in the same office than you can easily become very lonely, as an introvert I applaud at the opportunity of gaining better control over that.</p>
<p>But remote is not easy and while it's becoming more and more popular, most teams are not ready for it. Remote is so much more than just not coming to the office every now and then. If your processes and ways of working don't support remote workers as first-class citizens, you'll end up being miserable on both sides.</p>
<h3>Four steps for better remote work</h3>
<ol>
<li>
<p>Asynchronous communication</p>
<p>One of the biggest challenges in remote work, especially if the team is spread over multiple timezones is communication. You can't just tap on the shoulder of your colleague whenever you need them, so you need to become good at communication that spans over time.</p>
<p>Investing in proper tools helps a lot. Instant messaging with <a href="https://slack.com/">Slack</a> or <a href="https://www.flowdock.com/">Flowdock</a>, project management with <a href="https://trello.com/">Trello</a> or <a href="https://www.atlassian.com/software/jira">JIRA</a>, code communication with <a href="https://github.com/">Github</a> or <a href="https://bitbucket.org/">Bitbucket</a> and actually functioning conference calls with <a href="https://zoom.us/">Zoom</a> are some of my favorites.</p>
<p>But tools won't help you if you don't learn to embrace the new way of thinking. I grew up in the world of IRC so text-based asynchronous communication is natural to me, often even more natural than face-to-face communication.</p>
</li>
<li>
<p>Default to remote</p>
<p>Many companies that are now taking their first steps into remote work are obviously starting it with baby steps and there's nothing wrong with that. But to be successful in remote work, remote has to become the default.</p>
<p>If you need to ask permission to work remote or make arrangements for when you work remote beforehand, you end up with a work culture where individual productivity may rise but the collaborative productivity decreases as people just end up waiting for the remote worker to come to the office "tomorrow".</p>
<p>By defaulting to remote, you need to build your processes and ways of working around being a productive team even, and especially when, you don't hang out in the same space.</p>
</li>
<li>
<p>Socialize</p>
<p>Working remotely can become a lonely grind after a while. Without the constant social encounters that you get in the office, you might end up staying home in your pajamas all day long. Socializing with your coworkers (through instant messaging or video calls) to talk about also other things than strictly work helps build culture and team cohesion. Socializing with other people in cafes, co-working spaces or meetups gives you the physical connection to other people.</p>
</li>
<li>
<p>Trust</p>
<p>Remote work will never work if there's no trust. If your employer doesn't trust that you do work when they can't see you, there aren't many good ways to make it work. If you don't trust your teammates and assume good intentions, there will be a lot of assumptions that create unnecessary conflicts between you and hurts the performance of the team.</p>
<p>Personally I believe that the only working way is that trust is given, not earned. You start by trusting the people you work with and only if things go haywire, you give up on that trust. If you don't do that, you end up second-guessing and doubting people. When trust needs to be earned, it's very difficult to decide at what point the trust is earned. Because you know, it could always be the next time when they let you down.</p>
</li>
</ol>
<h3>Future</h3>
<p>I don't know when I'll take the leap to a full-remote, digital nomad lifestyle. I hope it happens soon but right now, I'm pretty satisfied practicing. Figuring out the optimal collection of things to own, learning to adapt to new apartments and neighbourhoods faster, honing my skills at work and saving money are my focus points right now.</p>
The Reset Button
2018-06-29T00:00:00Z
https://hamatti.org/posts/the-reset-button/
<p>Look around you. Of all your things, how many of them were a result of a thought-out process? And how many are just a result of an accumulation that started when you were a teenager?</p>
<p>For me, getting into minimalism was kind of a way to hit the reset button. I was 29 at the time and almost everything I owned was just a continuation of accumulation. Especially coming from a poorer family (<em>we were not very poor but definitely had to count the money when deciding what to buy</em>), many things were bought because they were affordable.</p>
<p>When, at the age of 19, I first moved out from home to my own apartment, I gathered together what I could. My bed from home, my older sister's old sofa. A cheap desk from IKEA, a shelf from my late grandparents. My kitchenware were mostly from a store I used to work at the time, that a dissatisfied customer returned and I got to take home. There was never any interior decoration: just a random collection of furniture.</p>
<p>When my first TV broke, I went to the store and bought the cheapest one I could buy. I didn't think of the features I needed or wanted, I just bought what I could. That's how you accumulate stuff. Little by little, you gather stuff from wherever you can. Every time I moved to a new apartment, I just tried to find an apartment I could afford and then hope I could fit all my stuff into it. It didn't really matter how it looked like or if it was a good fit to me: location and rent were the factors I took into account.</p>
<hr />
<p>I believe we all sometimes dream of starting fresh. But it's really hard and expensive, especially if you plan to get new versions of everything you own. Slowly, partly by accident, I started to realize I needed that. So I'll live this summer - and maybe even longer - without my own apartment, taking care of apartments as my friends and colleagues travel. This gives me time to think what I really want. To plan, to look for and to not make too many compromises.</p>
<p>It would not have been possible without minimalism. If I had a truck full of stuff, I couldn't live in random apartments for a few weeks at the time. It's also very difficult to get figure out which of your stuff is important when you're surrounded by them. That is why <a href="https://www.theminimalists.com/packing/">a packing party</a> is such a great way to start. Forcing yourself to take a break from your stuff and only bringing in back what you really desire, it gets easier.</p>
<blockquote>
<p>After three weeks, 80% of my stuff was still in those boxes. Just sitting there. Unaccessed. I looked at those boxes and couldn’t even remember what was in most of them. All those things that were supposed to make me happy weren’t doing their job. <cite>- <a href="https://www.theminimalists.com/packing/">Ryan Nicodemus, Packing Party: Unpack a Simpler Life</a></cite></p>
</blockquote>
<p>In a nutshell, a packing party means you pack all your stuff like you're moving. Then, one by one you bring those items back to your life. Some people take it to the extreme and only allow themselves to bring back one item per day.</p>
<p>I've been doing a version of this now twice: first, with my television that I wasn't sure I was ready to part ways. I packed it for three months and agreed with myself that I can take it back any day I want but if I don't unpack it for three months, I'll donate it. Second, right now I have some stuff in a warehouse. There's a bed, a desk, a chair, couple of shelves and a few boxes of stuff. The longer I live without them, the less likely it is I will bring them back to my life.</p>
The Cost of a Purchase
2018-06-22T00:00:00Z
https://hamatti.org/posts/the-cost-of-purchase/
<p>Last week I had an interesting conversation with a friend. We started talking about minimalism and getting rid of excess stuff as she was moving to a new apartment. The long-winding discussion led to a topic of how I have changed my perception of the cost of things. Back in the day I was mostly measuring everything by their monetary value: <strong>do I have enough euros or dollars in my bank account to make this purchase</strong>. As I got older and my salary increased, suddenly very few everyday items became out of reach. That's why my house started to fill with stuff that I thought I needed but didn't actually even spend another second thinking about my needs.</p>
<p>Now I consider other aspects than money: <strong>I think about what I need to get rid of to make the new purchase fit into my backpack and I need to think about the lifetime expense of owning something</strong>. Suddenly I'm not filling my house with 5€ trinkets just because they were on sale. Or buying a new tv just because I was able to financially pay for it.</p>
<p>I have never been good at waiting or sleeping over night before purchasing stuff. When I wanted or felt that I needed something, I just bought it. Limiting my life into a backpack, I have found new superpowers in this realm. I have been thinking about renting an apartment for a while now but so far I haven't figured out what I want. I would love to get a palm-size video projector since I love watching movies and playing games on a big screen. Instead of buying the first one I found (like I would have done in the past), I've been doing some research and trying to find compelling reasons why I absolutely need it. So far I haven't been able to convince myself.</p>
<p>For long I was a sucker for free stuff. I participated in all sorts of competitions and quizzes in hopes of winning something usually completely useless. I collected all sorts of marketing swag that companies gave for free, just because I made the decision based on the financial ability and not by the burden of ownership.</p>
<p>I have also started to borrow things to see how much I would use something before buying. For the past two weeks, I've been playing with Nintendo Switch I borrowed from a friend. I wanted to buy one since it's small, fits into my backpack and I love playing games. Since Playstation doesn't fit into the bag, Switch was my next choice but now after playing with it for couple of weeks, I'm not sure I actually want to have one cluttering up my bag.</p>
Space has a Tendency to Fill Itself
2018-06-15T00:00:00Z
https://hamatti.org/posts/space-has-a-tendency-to-fill/
<p><a href="http://articles.latimes.com/2014/mar/21/health/la-he-keeping-stuff-20140322">An average US household has 300,000 items</a>. I would argue that it's a lot. But looking at my own childhood in Finland, it's actually not surprising. We tend to gather a lot of stuff. And there's a perfectly reasonable explanation for it: <strong>space has a tendency to fill itself</strong>.</p>
<p>How many times have you packed something and noticed that there's still a little bit room in a box or a suitcase? And then you started thinking <strong>what more could you fit in</strong> since you have all this extra space. I have found myself thinking about that a lot. When I started decluttering my stuff, I caught myself multiple times thinking <em>"I'll give myself this box for board games, everything that doesn't fit in has to go."</em> Once you start going down that path, you will eventually fill every space you can find.</p>
<h2>The Space</h2>
<p>How about you start thinking from a different perspective? Instead of finding an apartment or a house and thinking about how to fill it, start thinking about what you need and love to have and find a place that fits those. Or first gather your belongings for a trip and then find a suitable case or bag for them.</p>
<p>It's not easy. Even after a year I still find myself thinking from the perspective of the space and not so much from the perspective of the needs. If you live alone, do you really need 8 large plates, 8 small plates, 8 bowls and 8 champagne glasses? When was the last time you actually used all of them at the same time? If you regularly host dinner parties, great, keep them. If not, I suggest you think about why you own so much. That example is from my life. The only effect that having 8 of each had was that my sink was always full of dirty dishes.</p>
<p>I'm now 30 and for the entirety of my adult life, I have moved into apartments first and started thinking how can I fill them after that. Only recently have I started to slow down and actually think about what I want and need first. That's why I'm currently living a domestic nomadic lifestyle, living 3-4 weeks per apartment as my friends travel until I can figure out what I actually want. But that's a story for another time.</p>
My love-hate relationship with PHP Arrays
2018-06-13T00:00:00Z
https://hamatti.org/posts/my-love-hate-relationship-with-php-arrays/
<p>I’ve been programming PHP for most of my life, almost 20 years now. While PHP has many downsides, horrible history, and terrible reputation, it has improved a lot with PHP 7 and modern frameworks like <a href="https://laravel.com/">Laravel</a> that make it really enjoyable to develop.</p>
<p>However, I still wish that there would be a “PHP2", a rework of the original one boldly breaking the backward compatibility and fixing some of the underlying weirdness.</p>
<p>In the modern world, where JSON is the de facto format for passing data around in the web, PHP has one downside that makes me mad. Instead of having <strong>arrays</strong> and <strong>objects</strong> like Javascript or <strong>lists</strong> and <strong>dictionaries</strong> like Python, it only has <strong>arrays</strong>. Doesn’t sound too bad, right? Let’s dive in.</p>
<h2>Array is an array</h2>
<pre class="language-php"><code class="language-php">$array = [1,2,3,4,5];
echo json_encode($array);
// Echoes: [1,2,3,4,5]. Wonderful!</code></pre>
<p>Since PHP 5.4, you’ve been able to create PHP arrays with a shorthand developers from other languages are able to understand immediately. It will create an array with values defined between [ and ] and when you encode it to JSON for passing through an API, you get a JSON array. Wonderful. <em>(I started developing with PHP 4 so that was a very welcome improvement)</em></p>
<p>You can do all the basic array operations for these arrays: sorting, popping, mapping, reducing, etc. Life is good.</p>
<h2>Array is an object</h2>
<pre class="language-php"><code class="language-php">$object = [
'name' => 'Jane Doe',
'country' => 'US',
'age' => 25
];
echo json_encode($object);
// Echoes: {"name": "Jane Doe", "country": "US", "age": 25}. Just like we love it !</code></pre>
<p>You can also create key-value stores with the fat arrow notation. Using the same shorthand, you define a key and a value and life is good. You can sort the new array either by value or key, you can loop over it with foreach and so on. All the goodies you’re used to with maps, objects or dictionaries in other languages.</p>
<h2>Array becomes an object</h2>
<pre class="language-php"><code class="language-php">// Let's modify an array
$array = [1,2,3,4,5];
unset($array[1]);
echo json_encode($array);
// Echoes: { "0": 1, "2": 3, "3": 4, "4": 5 }. Wait what. It's no longer an array</code></pre>
<p>The trick here is that actually, all arrays in PHP are key-value stores. So when you initialize an array with <code>[1, 2, 3, 4, 5]</code>, you actually create a key-value store of <code>[0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 5]</code>. So when you use json_encode to do the encoding, it will return a JSON array if and only if:</p>
<ol>
<li>The keys of the array are 0 to n, AND</li>
<li>The keys are in order from 0 to n</li>
</ol>
<p>otherwise it will return a JSON object. So running operations to modify your array can cause the keys to mess up and you end up getting something you didn’t expect.</p>
<h2>Array sometimes becomes an array and sometimes an object</h2>
<pre class="language-php"><code class="language-php">$numbers = [0 => 100, 1 => 300, 3 => 400, 2 => 150];
$letters = ['a' => 10, 'b' => 200, 'd' => 300, 'c' => 100];
echo json_encode($numbers);
// Echoes { "0" => 100, "1" => 300, "3": 400, "2" => 150 }
// Let's sort the array by keys and try again
ksort($numbers);
echo json_encode($numbers);
// Echoes [100, 300, 150, 400]
echo json_encode($letters);
// Echoes { "a": 100, "b": 200, "d": 300, "c": 100}
ksort($letters);
echo json_encode($letters);
// Echoes { "a": 100, "b": 200, "c": 100, "d": 300 }</code></pre>
<p>The scary part of PHP, and a cause of a lot of bugs, is when you can run certain array operations to different arrays with similar structure and the type of the JSON encoded is different.</p>
<p>If you sort an array with numeric, sequential and unordered keys, you turn from object to an array. If you sort an array with numeric, sequential and ordered keys, it remains an array. Whatever you do for non-numeric or non-sequential array, it will stay an object.</p>
<p>I think that whenever a normal operation run on a dynamic data and the type of the end result (<em>well, technically the return type of json_encode is always string but you know what I mean</em>) is sometimes different, we’re in very murky waters.</p>
<p>Having different types for sequential arrays (like <code>[1, 2, 3, 4]</code> and associative arrays <code>[0 => 1, 1 => 2, 2 => 3]</code>) is definitely on my wishlist for Santa this year.</p>
<p><strong>Note from 2019-02-09</strong></p>
<p><a href="https://hamatti.org/posts/php-needs-its-own-es6/">I wrote a blog post about this topic suggesting an ES6-for-PHP type of solution.</a></p>
What living a minimalistic life has taught me
2018-06-08T00:00:00Z
https://hamatti.org/posts/what-living-a-minimalistic-life-has-taught-me/
<p>About a year ago I learned about minimalism from Youtube videos and <a href="http://theminimalists.com/">the two dudes who call themselves the Minimalists</a>. In the end of June last year, I wrote <a href="https://hamatti.org/posts/intentional-and-multifunctional-how-i-got-rid-of-most-of-my-belongings">a blog post</a> about the first steps in my journey. After having a fruitful discussion about the topic with a friend on Wednesday, I decided to write a follow-up based on my experience and learnings from the past year.</p>
<p>A lot has happened in a year. Last summer I got rid of a lot of stuff I didn’t really need: started with excess clothes and TV and the snowball kinda started rolling from there. I moved to Helsinki in April and did two new huge rounds of decluttering.</p>
<h2>Backpacker’s life</h2>
<p>Currently I’m living a backpacker’s life, kinda accidentally. When I started my new job in new city in April, I didn’t want to get a new “permanent” apartment until I’d figure out what I really wanted. With most apartments having a minimum lease of 12 months and being burned with that way too many times, I took another approach.</p>
<p>A friend was traveling abroad so he offered me his room for the first two months. Quickly after that, I learned that another friend was gonna travel for three weeks and even before I moved into his apartment, I found one similar deal for the following month. Everything I use I carry in my traveling backpack and my smaller daily backpack — and I’m loving it.</p>
<p>My goal is to be able to become a digital nomad: traveling around the world while working remotely, learning from new cultures and experiences the wonders of the world. But I had to practice it first — if I couldn’t do it in Helsinki while having a regular job, how could I do it in a more stressful environment.</p>
<p>Having friends who travel a lot helps in this lifestyle. There’s always a startup entrepreneur or a traveller in my network who’s planning on longer trips around the world.</p>
<h2>Less stuff == less stress</h2>
<p>I’m happy to say I’m enjoying life to the fullest right now. I do still have a warehouse storage for my furniture and bunch of boxes I haven’t figured out yet. But I haven’t used that stuff in two months now and I haven’t missed anything from there.</p>
<p>With less stuff, you get less stress. Less cleaning up to do, less dishes to wash, less decisions to make. My kitchenware consists of <a href="https://us.monbento.com/en/bento-box-monbento-mb-original-black-made-in-france.html">a Monbento bento box</a>, one pair of chopsticks, <a href="http://www.dropp.fi/tuotteemme/dropp-x-sigg-robin-falck">a Dropp x Sigg water bottle</a> and a large mug. In my warehouse I have a pot, a cutting board and a frying pan but for now, I’ve been able to use those from the apartments I’m living in. With that collection of stuff, I’ve been able to eat everything I need without ever having a sink full of dirty dishes.</p>
<h2>No more stuff for other people to see</h2>
<p>One thing I kinda knew but never had the courage to admit to myself nor others is that I owned a lot of stuff for other people to see. I had my collection of Ghibli movies on DVD that I never watched (thanks to online streaming services) but wanted to keep in my shelf so if someone visited, they could see that I love Ghibli.</p>
<p>I kept my old Slush crew t-shirts so that if someone asked, I could show them or wear them in alumni meetings. I still do have two Slush t-shirts and a Slush hoodie ’cause they are great and ecological <a href="https://www.purewaste.org/">Pure Waste quality</a>.</p>
<p>Getting rid of those items, I freed so much space — both physical and mental — for more important things. No longer do I care about what other people might think when they see my stuff. Because the fact is they never cared, it was all just in my head.</p>
<h2>It’s different</h2>
<p>If someone would have told me 5 years ago that I would be living without a permanent address or apartment with everything I own living in my backpack and actually enjoying it, I wouldn’t have believed. I used to enjoy rules and structure and stable over adventurous but I’m so happy I learned to embrace the unknown.</p>
<p>Learning how to work productively without large monitors and bunch of peripherals has helped me become more flexible and given me more freedom. I can work from a kitchen, a cafe, a co-working space or an office equally. And I’ll always carry what I need to get stuff done.</p>
<p>Different things work for different people and the amount of stuff isn’t really what matters. So don’t take this as a guide to getting rid of everything and being happy because it’s not. You might be miserable. Heck, even I might become miserable when my life situation changes. That’s why it’s important to think about the <strong>why</strong> over the <strong>how</strong>.</p>
Beginning of a Journey
2018-06-08T00:00:00Z
https://hamatti.org/posts/beginning-of-a-journey/
<p>My journey as a minimalist started a year ago. For quite a while I had been a fan of the Tiny House movement and sometime last summer I ended up watching Youtube videos about minimalism and quickly found out about <a href="http://theminimalists.com/">The Minimalists</a>.</p>
<p>Until that point, I had lived with a lot of stuff. Back home, we had a nice house full of stuff. The garage was unusable because of the amount of stuff. All the closets were filled with boxes and piles of stuff. It was the life I knew. So when I grew up and moved to my first own apartment, I brought with me a lot of stuff. And for 10 years, my small studios were filled with things I never used. At some point, I moved three or four times without even fully unboxing my moving boxes. It still took a long time — and the help of amazing Youtubers — to realize that I could get rid of that stuff.</p>
<h2>The Beginning</h2>
<p>I figured it wouldn't be too hard, so I decided to start from my clothes. I didn't feel particularly interested in clothing: I just wore whatever I happened to have at hand. <strong>That's when I faced the first obstacle.</strong> I was emotionally attached to pieces of cloth. Little by little, I was able to go through my closet. I ended up donating over 70% of my clothes to charity. It took another couple of rounds later in the year to get down to what I now have but it was a good start.</p>
<p>Then I wanted to get rid of the TV but as a big movie fan and a gamer, it felt bit too radical to just sell my TV. So I decided to put it into a box for three months with a promise that if, after those three months, it would still be untouched in the box, I could sell it. I never looked back. I ended up donating it to a local non-profit and haven't missed a TV ever since. I still watch Netflix and other streaming services but I do it with my laptop.</p>
<p>Suddenly things started to happen. After not having a TV, I didn't need a tv stand, a sofa nor a coffee table. For the first time, I had some space in my apartment.</p>
<h2>The Continuation</h2>
<p>During my first year, I went from needing 2.5 vans for moving to living an adventurous life with a backpack and apartment hopping every month to live in another neighborhood. I'll share later what I carry with me and how I have organized my life but I basically live with a hiking backpack and a smaller daily backpack that I use when I'm not traveling.</p>
<p>This blog is a collection of my thoughts about minimalism, simplifying and living a more stress-free life. I hope to inspire and challenge you to think about your approach to life.</p>
<p>If you're interested in learning more about a minimalist lifestyle and my journey, you should come back on Fridays for new posts!</p>
Do you want to learn new stuff?
2018-05-23T00:00:00Z
https://hamatti.org/posts/do-you-want-to-learn-new-stuff/
<p>Continuous improvement, life-long learning, staying on top of your game are all things very close to my heart. It's not always easy though: sometimes your life (personal and professional) takes a detour and you end up in a situation where your skills get rusty.</p>
<p>I was in that position almost 3 years ago. I switched my full-stack developer career to community management, moved to Turku and worked on a non-technical job for two years. I wasn't ready to let go of my technical competences however. So I did what every knowledge hungry youngster does: I started inviting people smarter than me to tell their stories and share their wisdom.</p>
<p>When I started <a href="https://turkufrontend.fi/">Turku ❤️ Frontend</a> in December 2015 with Teemu, my original idea was to gather together four people who were smarter than me and who'd like to eat lunch once a month and talk about tech. That never happened. Instead, something even cooler did.</p>
<p>Others found that valuable as well: first we hit 100 people, then 200 people and right we are over 400 frontend developers, designers, hobbyists and students from the Turku area. And couple of dozen amazing companies helping us make it a reality. People have found jobs and companies have found professionals for employment or project work. And that makes me so happy. We even convinced people to spend half a day contributing to open source in February.</p>
<p>But at the core of the community has been my personal desire to learn. Today I had the pleasure of sitting in the front row listening to Juho Nurminen talk about web security and Margarita Obraztsova talk about the importance of documentation. In the past 23 meetups, we've had <a href="https://turkufrontend.fi/speakers">almost 50 top talents</a> from Turku, Tampere and Helsinki and I've had the opportunity to learn from them. Just by asking for a couple of hours of their time.</p>
<p>So if you want to learn from the best, my recommendation is to start a meetup: organize an event, ask a local company to host it, invite the best people you want to learn from and tell about it to your friends. That's all it takes.</p>
<p>In addition to Turku ❤️ Frontend, I had the luxury of spending two years organizing events and accelerator programs for startups. I absorbed hundreds of hours of expert knowledge and experience through keynotes, workshops, hackathons and 1on1 coaching sessions with startups. That's something I would have never been able to access from the other side of the table.</p>
<blockquote>
<p>What do you want to learn today?</p>
</blockquote>
Help your customers become better
2018-03-13T00:00:00Z
https://hamatti.org/posts/help-your-customers-become-better/
<p>Engagement and churn are topics that companies have to think about all the time. How to optimize the on-boarding to keep your users in the platform, how to make your product either addictive enough or providing enough value to get them return.</p>
<p>A customer who doesn’t know enough: either about the field they are working on or your product, uses your product less and is a churn-risk every day. On the other hand, a customer who is a power-user and expert in what they do get more value out of their tools and are willing to invest in them.</p>
<p>Investing into the customer education beyond the normal quick on-boarding by sales or customer care is a strategy that pays off in increased engagement and revenue. Let’s look at a couple of examples of how you can do it.</p>
<p><img src="https://hamatti.org/assets/img/posts/help-your-customers-become-better/event.jpeg" alt="Photo by Edwin Andrade on Unsplash" /></p>
<h2>Smartly Connect</h2>
<p><a href="http://smartly.io/">Smartly.io</a> is a Finnish company that builds tools for the world’s top Facebook and Instagram advertisers. They help their customers getting the best possible result with their digital ads.</p>
<p>Their pricing is based on the monthly ad spend on Facebook’s ad platform. So it’s easy to see the correlation: the better ads their customers create, the more they are willing to spend, thus increasing the revenue of Smartly. They benefit from their customers being the best experts in social media advertising.</p>
<p>Smartly has a great Customers team that helps their customers not only by fixing issues in the platform but actually helping them optimize their ads and become better. And in addition to that, they started running <a href="https://www.smartly.io/blog/connect-events-nyc-paid-social-experts">Smartly Connect events</a>.</p>
<p>Smartly Connect brings together top advertisers in the area to learn and share knowledge. They also get a direct access to their customers for feedback and learning from them so they can further build the platform. For a customer, attending Smartly Connect gives access to industry experts, Smartly’s product team and tons of valuable knowledge to apply to their daily work.</p>
<p><em>(Disclaimer: I used to work at Smartly in 2015)</em></p>
<p><img src="https://hamatti.org/assets/img/posts/help-your-customers-become-better/slack.jpeg" alt="Photo by Scott Webb on Unsplash" /></p>
<h2>Buffer Slack</h2>
<p>Working in the realm of social media is also <a href="https://buffer.com/">Buffer</a> who has a different kind of approach to educate their customers. Buffer runs a SaaS service helping social media managers to queue up their posts, share posts easily across different medias (Facebook, Instagram, Twitter, LinkedIn, Pinterest and Google+) and gain access to post analytics.</p>
<p>The education part of the puzzle for them is <a href="https://buffer.com/slack">a global Slack community of digital marketing experts</a>. Currently a community of almost 5000 experts and enthusiasts, Buffer Slack is a perfect place to learn, teach and network. In the Slack they have weekly activities such as a Community Mastermind every Thursday where members share their insights, experiments and learnings from the previous week and offer each other ideas and support.</p>
<p>While a Slack community is not a direct sales channel, building a community around your current and future users is a great way to engage and upsell with a long-term strategy. As I mentioned in the introduction, customers who are experts in what they do, tend to use their tools more effectively. And it’s a great way to gain direct feedback from your most passionate users.</p>
<p><em>(Disclaimer: I am a happy Buffer user and <a href="https://open.buffer.com/community-leaders/">a Buffer Slack Community Host alumni</a>)</em></p>
<p><img src="https://hamatti.org/assets/img/posts/help-your-customers-become-better/coder.jpeg" alt="Photo by Alex Kotliarskyi on Unsplash" /></p>
<h2>Twilio Developer Evangelists</h2>
<p>For companies building tools aimed for developers, evangelism/advocacy team is a good approach. <a href="https://twilio.com/">Twilio</a> builds an API service that allows you to connect via SMS, voice and video.</p>
<p>API technology is bit more complex to start using than the tools that for example Buffer or Smartly mentioned earlier offer. So it makes sense to ensure that your target audience knows how to use it and inspire new, potential customers about the opportunities.</p>
<p>Last month I talked with a couple of Twilio Dev Evangelists about their job and the approach Twilio has in helping developers to integrate Twilio to their applications. By being active experts in conferences, meetups, workshops and hackathons they provide insights and direct help for developers who want to get started or struggle with the integration.</p>
<p>Imagine a situation where you’re working on an integration with platform X and you run into issues. In which case would you be more likely to become a happy, paying customer: the one were you know a dev evangelist from the company who can help you (or one is in the event giving you a hand) versus one where you end up googling for Stack Overflow posts and banging your head to the desk.</p>
<p><em>(Disclaimer: I’m a new Twilio user who loves how easy it makes my life. I recently built CVilio — a resume via SMS.)</em></p>
<p><img src="https://hamatti.org/assets/img/posts/help-your-customers-become-better/food.jpeg" alt="Photo by Entis" /></p>
<h2>Recipes</h2>
<p>It’s about lunch time when I’m writing this and that brings me to my next example: recipes. You can have the most brilliant food ingredient or the best sauce but if nobody knows how to combine it with other things to make a tasty meal, you’re missing out on sales.</p>
<p>Finnish market chain Kesko has a store brand called Pirkka with a couple of thousand different products. In their stores, they distribute a bi-monthly newsletter full of delicious recipes and have <a href="https://www.k-ruoka.fi/reseptit">an online bank of 7000+ recipes (in Finnish only)</a>.</p>
<p>It’s even more important for smaller brands: a Finnish startup <a href="https://www.entis.fi/">Entis</a> is creating the future of insect food and a regular Joe probably has no idea how to cook with crickets (I sure don’t). Entis runs an email newsletter with recipes for their forward-thinking customers. This way they can not only educate their customers but also bring their product to their mind again and again.</p>
<p><em>(Disclaimer: I’m hungry)</em></p>
Food Connects People - How to Hack Your Community with Food
2018-02-22T00:00:00Z
https://hamatti.org/posts/food-connects-people-how-to-hack-your-community-with-food/
<p>_One of the things I learned early on was that everybody has to eat. Most people eat lunch and it means taking a “mandatory” break from work and the busy-ness of business life. I’ve used that knowledge to my advantage by making a lot of connections by asking people for lunch. They don’t have to make room for meeting me and people are often happier when they eat.<br />
_</p>
<p>Here are some of the ways you can build your networks or strengthen your community with food.</p>
<p><img src="https://hamatti.org/assets/img/posts/food-connects-people-how-to-hack-your-community-with-food/breakfast.jpeg" alt="" /></p>
<h2>Community/Company Breakfast</h2>
<p>For the past few years I worked in a startup community Boost Turku. Every now and then we brought our alumni teams together for a shared breakfast. It was an opportunity for them to share what they are doing, ask for help or talk about the entrepreneurial life.</p>
<p>At <a href="http://smartly.io/">Smartly.io</a> we started our week by eating breakfast together as a team every Monday. During the breakfast, we went through some updates on how the business is going on. One of the concepts we piloted was assigning two people from the team each week to take care of the breakfast. So every week there was an unique twist.</p>
<p>If you have been active in the startup scene, you have most likely seen breakfast events all around. It’s one of the easiest ways to bring people together to share ideas and meet each other.</p>
<p><img src="https://hamatti.org/assets/img/posts/food-connects-people-how-to-hack-your-community-with-food/lunch-lottery.jpeg" alt="" /></p>
<h2>Lunch Lottery</h2>
<p>Your community is growing and more and more people don’t know each other. A lot of valuable <a href="https://en.wikipedia.org/wiki/Tacit_knowledge">tacit knowledge</a> is lost when new and old people don’t meet. This can cause you to miss opportunities to to re-invent the wheel.</p>
<p>In <strong>Lunch Lottery</strong>, a computer pairs up people with each other once a month. These people then eat lunch together. It’s simple but fun! Often people also share photos or stories with the community.</p>
<p>How you mix-and-match people is up to you. You can have everyone in the same pool or you can divide people based on seniority. The latter enables you to match seniors or alumni with new people. Or you could help people working in different departments to meet each other.</p>
<p>If your community is in Slack, I recommend checking out <a href="https://www.donut.ai/">Donut.ai</a>. Another great app to look at is <a href="https://www.workwell.io/app/">Workwell</a>. It’s less about lottery and more about matching people with shared interests within a company.</p>
<p><img src="https://hamatti.org/assets/img/posts/food-connects-people-how-to-hack-your-community-with-food/lunch-circle.jpeg" alt="" /></p>
<h2>Lunch Circle</h2>
<p>I love meeting new people but finding great people is sometimes difficult. I have vast networks and I know my friends have amazing friends. Couple of years ago I decided that I want to meet new people on a casual no-agenda way. That’s when I started my first <strong>Lunch Circle</strong>.</p>
<p>Gather together a small group of friends and agree to eat lunch together once a month. The gist is that each time, one member of the circle brings an extra person to join the lunch. This way you get to meet people your friends love.</p>
<p>We ran it with three people and every time the fourth person was from one of our networks.</p>
<p>This is a great, personal way to expand your networks. It works especially well when you move to a new city or country. And everyone benefits by getting to meet new people.</p>
Communities in Marketing
2018-02-16T00:00:00Z
https://hamatti.org/posts/community-in-marketing/
<p><em>This blog post is a recap based on a talk I gave in Fraktio's Perjantaipresis in February 2018. You can <a href="https://www.youtube.com/watch?v=ApDdtwHyLtg">watch the recording here</a>.</em></p>
<p>Communities are fantastic, I love them. We have had communities for as long as humans has been in existence but in the past few years, it has become a buzz word. With the Internet and tools like Facebook, Twitter, Discord and Slack providing access to everyone globally, it’s no wonder these communities have started to pop up.</p>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/marketoonist.jpeg" alt="Source: https://marketoonist.com/2018/01/blockchain.html • © Tom Fishburne" /></p>
<p>Many companies still treat communities as something they’d like to have (bit like AI and blockchain) but are not really willing to dedicate resources towards it. It just falls into someone’s lap to do in addition to their regular job. I wanted to showcase a couple of examples of how you can use communities to promote your business.</p>
<p>These five case stories are based on my experiences — either as the one building the community or as the one being a member of the community. You can mix-and-match many of the features in these cases when building yours.</p>
<p>Communities are a long-term marketing strategy. You can’t expect results in mere weeks. But once you have a thriving community up and running, you can be sure people will tell their friends about the experience and return back to your product/service time after time.</p>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/case1.png" alt="" /></p>
<p><strong>Company & Friends</strong> is a strategy for building a community for your super-users (and making many people feel like one).</p>
<p>In Company & Friends, you start a Facebook group or Slack workspace or a physical regular gathering that is invite-only. Then you invite your <a href="https://en.wikipedia.org/wiki/Diffusion_of_innovations">innovators and early adopters</a> and make them feel special.</p>
<h3>What should you do?</h3>
<ul>
<li>Provide early access to your product/service and news about it</li>
<li>Hear their opinion (for example, you can do polls to ask what they would prefer)</li>
<li>Encourage them to share their experiences with each other</li>
<li>Offer help for their issues</li>
</ul>
<h3>What do you get?</h3>
<ul>
<li>Direct connection to people who love your product/service</li>
<li>(Usually very) honest feedback loop</li>
<li>An opportunity to prove your customer care is top-notch by solving problems in a semi-public venue</li>
<li>People who talk about your product with each other</li>
<li>People who want to invite their friends into the exclusive group</li>
</ul>
<h3>Extra reading</h3>
<p><a href="http://paulgraham.com/ds.html">Do things that don’t scale by Paul Graham</a></p>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/case2.png" alt="" /></p>
<p><strong>Community Leaders</strong> is a way to engage your community members in being a more active member and helping you run your community. You can call them Moderators, Hosts, Leaders, Ambassadors, or use a branded name for people who have special status in your community.</p>
<h3>What should you do?</h3>
<ul>
<li>Select the most active and friendly people in your community and give them a status</li>
<li>Offer extra perks like company swag, free access to paid product, connections in the industry, mentoring</li>
<li>Give people ownership over the community</li>
</ul>
<h3>What do you get?</h3>
<ul>
<li>Helping hands in running the community</li>
<li>New ideas and initiatives</li>
<li>Potential recruits from people who are already engaged in your community</li>
</ul>
<h3>Extra reading</h3>
<p><a href="https://open.buffer.com/community-leaders/">We Support A Slack Community of 4,000 By Creating Leaders, Here’s How by Arielle Tannenbaum</a></p>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/case3.png" alt="" /></p>
<p>Not all companies have products to build a community around. If you’re working in a field where it’s difficult to find talented people, you can use community as a <strong>Recruitment</strong> funnel. Build a community of professionals (including your current employees!) and let them know that you’re the best place to work at.</p>
<h3>What should you do?</h3>
<ul>
<li>Organize events: hackathons, workshops, conferences, board game nights, movie premieres — the sky is the limit: impress people</li>
<li>Genuinely offer knowledge and experiences to people: help them become better and help connect them with people in the industry</li>
<li>Focus on creating a great experience for employees: provide tools they need, give them autonomy and hear their opinion</li>
<li>Build relationships with people way before you are about to hire them</li>
</ul>
<h3>What do you get?</h3>
<ul>
<li>Goodwill and PR amongst the potential recruits</li>
<li>Less employee churn: no reason to leave a place that is good for them</li>
<li>People talking to their friends about your company</li>
</ul>
<h3>Extra reading</h3>
<p><a href="https://www.joelonsoftware.com/2006/09/06/finding-great-developers-2/">Finding Great Developers by Joel Spolsky</a></p>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/case4.png" alt="" /></p>
<p>You might want to be active in building a community but don’t really have resources for a full-time community. <strong>Partnerships</strong> with existing communities are the way to go. Professional communities, developer meetups and all sorts of conferences are out there for you to collaborate with.</p>
<h3>What should you do?</h3>
<ul>
<li>Host meetups, sponsor events, provide speakers</li>
<li>Let the existing community do the heavy lifting and in return, make their life easier with your contributions</li>
</ul>
<h3>What do you get?</h3>
<ul>
<li>Access to people relevant to your community</li>
<li>Exchange of services: money/expertise for exposure to community members</li>
</ul>
<h3>Extra reading</h3>
<p><a href="https://hamatti.org/posts/how-to-build-a-kickass-developer-community/">How to build a kickass developer community by me</a></p>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/case4.png" alt="" /></p>
<p>Communities don’t happen on their own without putting in resources. You can’t start a Slack workspace and expect the community to thrive. The exception to the rule are <strong>Natural Communities</strong>. They are built when people care about your product enough that they want to talk with like-minded people.</p>
<p>This is especially strong in gaming communities: people start Facebook groups and Discord/IRC servers just to share their experiences and tips for becoming better players and finding people to play with.</p>
<h3>What should you do?</h3>
<ul>
<li>Let them maintain the ownership of the community</li>
<li>Tell them you are there to help if needed</li>
<li>Offer freebies or specials to the people running those communities</li>
<li>Embrace, don’t conquer</li>
</ul>
<h3>What do you get?</h3>
<ul>
<li>Same as with Company & Friends approach: access to feedback and issues</li>
<li>Already existing Community Leaders who promote the product and build a community around it</li>
</ul>
<p><img src="https://hamatti.org/assets/img/posts/communities-in-marketing/bonus.png" alt="" /></p>
<p>In addition to these five cases, one extra approach I want to highlight is the first impression: <strong>Welcome</strong>. What happens when people join your community for the first time? Is there somebody welcoming them, is there someone helping them get started and connecting with people who share similar interests?</p>
<h3>What should you do?</h3>
<ul>
<li>Make sure everyone gets a warm, personal welcome. You can do it each time someone joins or once a week for new people</li>
<li>Ask people to introduce themselves to each other</li>
<li>Connect people you know in your community who share common interests</li>
<li>Let people know how to contribute and engage in the community</li>
</ul>
<h3>What do you get?</h3>
<ul>
<li>People who feel good about being part of the community from the very first moment</li>
<li>Higher return-rate</li>
<li>More engaged members</li>
</ul>
How I convinced 15+ companies to contribute to OSS
2018-02-07T00:00:00Z
https://hamatti.org/posts/how-i-convinced-15-companies-to-contribute-to-oss/
<p>Story time! On January 2nd I launched Turku Gives Back, a non-profit project to encourage local software companies to give back to open source. Last Friday, February 2nd 16 companies and bunch of individuals joined together, we made 20+ contributions to various open source projects.</p>
<p>I wanted to share how I came up with the idea, built the project and managed to make my contributions by encouraging others.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Opening them sources ☺️ <a href="https://twitter.com/hashtag/TurkuGivesBack?src=hash&ref_src=twsrc%5Etfw">#TurkuGivesBack</a> <a href="https://t.co/oNbgf81BVs">pic.twitter.com/oNbgf81BVs</a></p>— Arado (@WeAreArado) <a href="https://twitter.com/WeAreArado/status/959390843458158593?ref_src=twsrc%5Etfw">February 2, 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<h2>The Idea</h2>
<p>In the end of 2017, I was applying for a job at Github and was browsing through their blog and website and ran into <a href="https://opensourcefriday.com/">Open Source Friday</a>. What a brilliant idea. I knew that most companies didn’t make it a habit or part of their culture to contribute back to open source but on the other side there was a rise of companies rewarding their developers and designers for contributions.</p>
<p>Also, my personal relationship with open source wasn’t too shabby either and I wanted to do something to fix it. Instead of just spending a few hours contributing directly, I wanted to increase the impact by spending those hours building a project with hopes to attract others.</p>
<h2>The Execution</h2>
<p>I came across the website on Thursday evening and it stuck in the back of my head. By Friday evening, when I was walking home from the bar, my brain started to work and by the time I had reached home, I had come up with a plan I was super excited about: create a guide book for participants, website, press release and a list of companies to contact.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/turkugivesback?src=hash&ref_src=twsrc%5Etfw">#turkugivesback</a> <a href="https://twitter.com/hashtag/opensourcefriday?src=hash&ref_src=twsrc%5Etfw">#opensourcefriday</a> supported by <a href="https://twitter.com/turkufrontend?ref_src=twsrc%5Etfw">@turkufrontend</a> , pillimehut ja keksit starting soon ... 🤓 Waiting for the folks to arrive <a href="https://twitter.com/hashtag/WeareBCB?src=hash&ref_src=twsrc%5Etfw">#WeareBCB</a> <a href="https://twitter.com/hashtag/BCBMedical?src=hash&ref_src=twsrc%5Etfw">#BCBMedical</a> <a href="https://t.co/kTZbC7vMM5">pic.twitter.com/kTZbC7vMM5</a></p>— Tanja Repo 🦊 (@RepoRedHat) <a href="https://twitter.com/RepoRedHat/status/959369375844306944?ref_src=twsrc%5Etfw">February 2, 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I’m the kind of guy who gets going really fast. I didn’t really know what I was about to do but once the ball started rolling, I couldn’t stop. In three hours, I had built a website (using Squarespace, huge fan!), bought a domain, crafted a 6-page guidebook (using Keynote — my #1 tool for these kind of things), wrote a blog post, figured out the most important partners from our <a href="https://turkufrontend.fi/">developer community partner network</a>. I created a form through which companies could register (using Squarespace form tool with Google Drive sheets integration, one-click-wonder).</p>
<p>Then I had to wait. It was the turn of the year so I didn’t want my emails to be lost in the ever-growing pile of holiday emails. Finally on Jan 2nd, I got to launch. I hand-wrote 35 emails to CEOs, CTOs and other contacts in local software companies, published the blog post and started going wild on social media.</p>
<p>During the month leading up to the main day, I talked with bunch of developers and directors, pitching the idea and getting good feedback from the companies. I wrote <a href="https://hamatti.org/posts/5-reasons-to-contribute-to-open-source">another blog post</a> about the benefits of open source contributions and tweeted out whenever a company joined the campaign.</p>
<h2>The Results</h2>
<p>As I mentioned, we were able to gather together 16 companies who gave half a day (some even full day!) of their time making contributions to open source projects they felt important.</p>
<p>Some companies made contributions to their own code that they had open sourced, some made contributions to the tools and frameworks they use to run their business and some helped small side projects that make their life more enjoyable as developers and designers.</p>
<p>I’m so happy by the outcome. Much more was done to open source than I could have done alone. And hopefully we were able to give open source the attention it deserves in companies.</p>
<p>All together 24 open source contributions were made during the event. Check out the list of contributions here and check out some hyped tweets at <a href="https://twitter.com/i/moments/959433256386080768">our Moments</a>.</p>
<p>The general feedback from the companies was positive and for at least a month, open source was a discussion topic in the developer community in a new way. I had plans to contribute myself during the day but ended up spending my day in Twitter and email sharing the contributions people made. I did build a tutorial for how to integrate Twilio to your application the next day.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Fun day coding and contributing! Thank you <a href="https://twitter.com/turkufrontend?ref_src=twsrc%5Etfw">@turkufrontend</a> for organising <a href="https://twitter.com/hashtag/turkugivesback?src=hash&ref_src=twsrc%5Etfw">#turkugivesback</a> <br />Our <a href="https://twitter.com/hashtag/Turku?src=hash&ref_src=twsrc%5Etfw">#Turku</a> office enjoyed the <a href="https://twitter.com/hashtag/opensourcefriday?src=hash&ref_src=twsrc%5Etfw">#opensourcefriday</a> - with some pizza and snacks, of course🍕 <a href="https://t.co/WY11DGo2Tx">pic.twitter.com/WY11DGo2Tx</a></p>— Wunder (@Wunder_io) <a href="https://twitter.com/Wunder_io/status/959447468667744257?ref_src=twsrc%5Etfw">February 2, 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>From an idea to execution, marketing and finally the day, Turku Gives Back was a successful pilot. I learned a bunch of stuff about organizing something like this and thus, the next time it will be even better.</p>
5 Reasons to Contribute to Open Source
2018-01-10T00:00:00Z
https://hamatti.org/posts/5-reasons-to-contribute-to-open-source/
<p>Open source software has had an immeasurable impact on the modern software business. If you’re building anything for the web, you are most likely heavily relying on infrastructure, software and frameworks built on the open source model — allowing you to get productive with small overhead and benefiting from the efforts of the community.</p>
<p>Most of the servers in the world run either on Linux or FreeBSD operating system, source control is run by Git or Subversion, databases are Postgres, MySQL or MongoDB and web apps are built with React, Angular, Django, Ruby on Rails and other similar frameworks. What’s common to all of them? It’s all open source.</p>
<h2>1. Open Source contributions are an investment</h2>
<p>As we launched Turku Gives Back, we knew that one of the biggest reason for companies to skip comes down to money. Developers’ time is not cheap and choosing open source contributions over billable customer hours is not an easy decision to make.</p>
<p>That’s why it’s important to consider open source contributions as an investment to the tools that your business relies on. Most of us have probably encountered an issue with a tool that we’ve used: a bug, a missing feature or bad documentation. Building workarounds or spending hours figuring out how to use a feature is costly. Instead of just waiting for someone else to take care of the problem, invest little time to fixing the bug or improving the documentation and you will reap the rewards in the future.</p>
<h2>2. Open Source culture is a recruitment factor</h2>
<p>Hiring the top developers is a fierce competition and hiring new people is not cheap. For developers, the situation is great: we get to choose the companies we work in and work culture factors are playing a great part in that deal.</p>
<p>Flexible hours, having your voice heard and <a href="https://dev.to/lynnetye/what-developers-want-in-a-job-7hg">having a culture that values open source</a> are all features that developers value. We are big fans of <a href="https://spiceprogram.org/">Futurice’s Spice Program</a> that rewards employees for their open source contributions.</p>
<h2>3. Improvement of skills</h2>
<p><img src="https://hamatti.org/assets/img/posts/5-reasons-to-contribute-oss/futurice-screenshot.png" alt="" /></p>
<p><a href="https://futurice.com/blog/year-2015-in-company-sponsored-open-source">https://futurice.com/blog/year-2015-in-company-sponsored-open-source</a></p>
<p>As developers and designers, we need to constantly stay on top of the game by updating our skill sets and becoming better at executing our profession. Open source contributions are one opportunity to let your developers expand their skill set in a motivational way.</p>
<p>Often our projects at work are limited to a certain technology or domain and there’s not a lot of options for exploring and extending outside those domains. Open source provides a playing field that benefits the employee as well as the employer.</p>
<h2>4. Promotion / PR</h2>
<p>Imagine a scenario: your developer takes part in a meetup or a conference to talk about an open source project or contribution. Whether the listeners are fellow developers or potential customers, painting a picture of your developers as experts creates value for the company.</p>
<p>Quite often especially in software consultancy, you’re bound by some level of NDAs about your customer projects: you can’t really reveal any big secrets of the projects but with open source, you can share and brag to your heart’s content.</p>
<h2>5. Doing the right thing</h2>
<p>Finally, business is just a means to an end. We value open source for what it is: a common interest and a way to reach the goal of making the world a better place through software.</p>
<p>Given the impact that open source has had in the software world in the past decades, it’s easy to see the value for the society as well as for the business. It’s a long game, sure, but would any of us really want to live in a world where everything is a closed garden and we are at the mercy of a few companies.</p>
<p><em>Turku Gives Back is a non-profit project by Turku ❤ Frontend to challenge and encourage local software companies to spend half a day on Friday the February 2nd 2018 contributing to open source.</em></p>
<p><strong>Turku ❤ Frontend is a community of developers and designers interested in frontend web development and design. We run monthly meetups and organize other events like hackathons, programming competitions and projects like Turku Gives Back. Follow us on <a href="https://twitter.com/turkufrontend">Twitter</a>, register to join upcoming meetups in <a href="https://meetabit.com/communities/turku-3-frontend">Meetabit</a> and join the discussion on <a href="https://www.facebook.com/groups/turkufrontend/">Facebook</a> and <a href="https://turkufrontend.fly.dev/">Slack</a>.</strong></p>
How to build a kickass developer community
2018-01-08T00:00:00Z
https://hamatti.org/posts/how-to-build-a-kickass-developer-community/
<p>I was asked by a friend to share some tips and best practices from our two years of running the meetup so that others could replicate and prosper. While we can’t really give a silver bullet nor pinpoint exactly where we got it right, I wanted to share a few things I’ve learned while running Turku ❤ Frontend and growing it from 0 to 350+ members with fantastic partners and constant stream of amazing speakers.</p>
<p>Since being successful in anything is widely based on luck, timing and context, here’s a little backdrop on where we operate since I can’t really say that all these things work in all environments.</p>
<p><img src="https://hamatti.org/assets/img/posts/how-to-build-a-kickass-developer-community/margarita.jpeg" alt="" /></p>
<h2>0. Context</h2>
<p>We started Turku ❤ Frontend in a situation where Turku hadn’t had a tech meetup outside the game developer community in years. People weren’t so familiar with the meetup concept so we had to “teach” the community into a habit of being in a meetup group and giving talks and sponsoring our events.</p>
<p>On the other hand, we had an open playing field. There was no competition (even though I don’t believe that meetups should compete at all) and there were no expectations on what a meetup looks like.</p>
<h2>1. Know your people</h2>
<p>Choosing the theme of your meetup is crucial. If it’s too broad, no one really feels like it’s their community — if it’s too strict, you might not have a big enough audience.</p>
<p>What is important is finding your niche and sticking to it. Know what your people are interested in, work hard to find speakers and partners that are highly relevant and listen for feedback.</p>
<p>Like building anything else, the real way to know is to ask. You should talk with your community and target group and involve them. We are proud that our meetup can act as a stepping stone into giving talks — it’s always easier to start public speaking among your own community.</p>
<p>From what I know, nobody likes ads. So treat your developers with respect and be strict with the speakers — we make sure to tell speakers that we expect content based talks, not ones about your company. Use a slide and few minutes to mention that you’re recruiting, sure. But don’t go on for 30 minutes just advertising your product.</p>
<h2>2. Be persistent</h2>
<p>It’s always rough to start something new. It takes time to get things in motion and that’s how it was for us as well. The first year was a lot of practice and hard work: we got our speakers by hours of browsing LinkedIn, checking other meetups in the country and contacting former speakers and trying to convince our friends to give talks. Similar with sponsors: it took a lot of work and we didn’t get sponsors every month.</p>
<p>However, we haven’t skipped a single meetup so far. Luckily, we had an opportunity to host a sponsor-less meetup in a local co-working space so we were able to manage having meetups every month.</p>
<p>Now things are looking much nicer. We have more partners than we can host meetups (so we are constantly coming up with new projects so we can keep everyone in the loop) and we can easily build our schedule 4–5 months ahead since companies and people want to get involved.</p>
<h2>3. Ride the wave</h2>
<p>Once you get your community going, try to think ways to push forward. Meetups are great and there’s nothing wrong with sticking to them but if you want to build a community and strengthen the relationships between people, it’s good to try new things.</p>
<p>First year, we organized a hack day. Bunch of people came in on August for a Saturday of hacking with location data. Second year, we hosted a Code in the Dark event (an event that originated from the community and we helped out running it). Third year, we’re raising the bar: we start in January with Turku Gives Back open source day and in October, we are organizing a frontend conference. And we are constantly coming up with new ideas to activate the community and help developers and companies.</p>
<p>It’s always easier to improve when the ball is rolling. Look into improving the meetup experience as well: we are now planning on making some purchases so we can have a good AV system in-house and bringing in a new person into the core team to be in charge of recording the talks so we can share them to people who can’t make it to the events. And don’t be afraid to experiment. There’s really nothing to lose.</p>
<p><img src="https://hamatti.org/assets/img/posts/how-to-build-a-kickass-developer-community/vaadin-team.jpeg" alt="" /></p>
<h2>4. Community is people</h2>
<p>Remember that a community is nothing without the people. Give your community and its brand for people in the community to build something. I mentioned earlier our Code in the Dark event: it stemmed from the community and we gave a helping hand and the brand to run it.</p>
<p>From Day 1, we’ve had the strong ideology that Turku ❤ Frontend is a brand owned by the people in the community. Sure, in the first months or even years people might not do anything with it. But building that culture of openness and feeling of belonging to the community from the very beginning will shape your community in the long run.</p>
<p>It’s also important to have a great core team. Sometimes there are moments when you’re too busy or too tired and it’s important that you can share the responsibilities with fantastic people.</p>
<h2>5. Remember to have fun!</h2>
<p>Communities are fun. So make sure that you don’t take things too seriously.</p>
<p>You should be having fun and the people in your community should be having fun. For us, this community is equal parts building something amazing for the developer community we are part of and an opportunity to have fun with some of the most amazing people out there.</p>
<p>If you don’t enjoy the journey, you’re probably not gonna go on for long.</p>
Year in Review 2017
2017-12-17T00:00:00Z
https://hamatti.org/posts/year-in-review-2017/
<p>
Around the same time last year, I wrote that 2016 had been okay but I was
looking into a much greater year 2017. Right now, I’m not quite sure I
achieved that. So once again, I’m setting my hopes on year 2018.
</p>
<p>
If you want to reflect on your past year and plan your future year, I have
found that
<a href="http://yearcompass.com/" rel="noopener nofollow">Year Compass</a> is
a fantastic tool for it. It’s something I look forward doing every December.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/slush-2017.jpeg" class="kg-image" alt="A large group of people wearing white t-shirts, purple light show" />
</figure>
<h2 id="year-of-communities"><strong>Year of Communities</strong></h2>
<p>
This year was quite a successful one regarding the communities I worked with.
</p>
<p>
I started the year continuing my work at
<a href="https://boostturku.com/" rel="noopener nofollow"><strong><strong>Boost Turku</strong></strong></a><strong><strong>. </strong></strong>During the spring one of the highlights
was a third installment of Dropout Academy we organized — a hands-on workshops
series aimed to teach students and young entrepreneurs skills they’ll need. In
summer, I was the lead organizer of award-winning accelerator program Startup
Journey and got to experience the early stages of 7 companies in the program.
</p>
<p>
In June, I joined
<a href="https://buffer.com/slack" rel="noopener nofollow"><strong><strong>Buffer Slack community</strong></strong></a>
for digital marketing experts and started learning a lot, not only about
digital marketing, but also about remote work, productivity and freelancing
from amazing people I had weekly Skype calls around the world. In August I
started a 6-month term as a Community Host helping further build the community
that now stands at almost 4000 members world wide.
</p>
<p>
<a href="https://twitter.com/turkufrontend" rel="noopener nofollow"><strong><strong>Turku ❤ Frontend</strong></strong></a><strong><strong> </strong></strong>had a wildly successful second year. We
organized 9 meetups with 18 talks and a Code in the Dark programming
competition event while seeing over 50% growth in the community. In August I
gathered together a team with which we started organizing a conference for the
upcoming year and worked on building the program and contacting top speakers
around the globe.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/summer-of-programming.jpeg" class="kg-image" alt="People working on their laptops in multiple rows of tables, photographed from above" />
</figure>
<p>
I also continued teaching programming. During summer, together with
<a href="https://twitter.com/magdapoppins" rel="noopener nofollow">Magdalena</a>
and
<a href="https://fi.linkedin.com/in/snieminen" rel="noopener nofollow">Sami</a>
we ran our second
<a href="https://boostturku.com/boost-digit-presents-summer-of-programming/" rel="noopener nofollow">Summer of Programming</a>
program: a three-month course teaching the basics of web development (HTML,
CSS, Javascript) with about 100 people visiting the events. In September, I
launched
<a href="http://koodimentori.fi/en" rel="noopener nofollow"><strong><strong>Koodimentori.fi</strong></strong></a>
— my pro bono mentoring program for beginners and junior developers — with
first three mentees. I also coached in a couple of workshops throughout the
year.
</p>
<p>
And all the smaller things that really warmed my heart. The student trip we
organized to Spotify’s Stockholm office,
<a href="https://theshift.fi/" rel="noopener nofollow">SHIFT Business Festival</a>, <a href="https://slush.org/" rel="noopener nofollow">Slush</a>, coaching in
<a href="http://www.legaldesignsummit.com/brainfactory" rel="noopener nofollow">Legal Design Summit Brainfactory</a>
and plenty others.
</p>
<p>
All together I organized 129 events, gave 18 talks (about technology,
entrepreneurship, startups and programming) and helped a bunch of people find
work and companies find employees. Community wise, it was a good year.
</p>
<h2 id="cultivation-of-mind-and-body">Cultivation of mind and body</h2>
<p>
Doing all the things listed above took way too much time. In the beginning of
the year I set a challenge of reading 25 books and so far with less than two
weeks to go,
<a href="https://www.goodreads.com/user_challenges/7128573" rel="noopener nofollow">I have read 21</a>. My favorites this year have been
<a href="https://www.goodreads.com/book/show/30231806-goodbye-things" rel="noopener nofollow">Fumio Sasaki’s Goodbye, Things: The New Japanese Minimalism</a>
and Mark Manson’s fantastic
<a href="https://www.goodreads.com/book/show/28257707-the-subtle-art-of-not-giving-a-f-ck" rel="noopener nofollow">The Subtle Art of Not Giving a Fuck</a>.
</p>
<figure class="kg-card kg-image-card">
<img src="https://hamatti.org/assets/img/ghost//2020/12/may-august-2017.jpeg" class="kg-image" alt="On the left: me in May 2017 and on the right: me in August 2017, after losing significant amount of weight" />
</figure>
<p>
Last spring I got excited about minimalism which really helped me find some
balance in life. I
<a href="https://medium.com/@Hamatti/intentional-and-multifunctional-how-i-got-rid-of-most-of-my-belongings-181685b69e88" rel="noopener">decluttered a lot of my stuff</a>
and simplified my life all together. Between May 16th and August 17th I lost
18 kgs (40lbs) just by fixing my eating habits and walking 10–15 km (6–9
miles) a day.
</p>
<p>
I felt amazing at the end of the summer but unfortunately the stress of
starting a new job and being back in the market (a fancy way of saying I’m
unemployed) 5 weeks later derailed me and I ended up gaining back too much of
the weight and bad habits.
</p>
<p>
My year’s biggest improvement was the loss of my panic disorder. Due to
finding more balance in both in mental and physical health, in July the
symptoms completely disappeared.
</p>
<p>
I also got to travel a bit after a year of being bit stuck, mostly due to the
panic attacks. I went to Stockholm, Copenhagen, Hamburg, Berlin (even though I
spent my entire trip being food poisoned in my hostel bed) and Tallinn. I hope
next year I’ll finally find a way to do even more traveling.
</p>
<h2 id="the-crisis">The Crisis</h2>
<p>
Of course no year is perfect. I had my fair share of stress, anxiety and fear
for future. After taking a detour from development for community management
for couple of years, I’m in a weird spot.
</p>
<p>
I’m having trouble finding work as a developer since I haven’t been actively
coding for the past two years but also on the paper, I don’t have enough
experience to do the community things I’ve been looking for. With zero money
in the bank, I’m gonna have a busy early January trying to find out my place
in the world again.
</p>
<h2 id="2018-year-of-me">2018: Year of Me</h2>
<p>
For the past two years I’ve been busy building communities and it has taken a
lot of time. Over 300 events in 700 days is nuts and way too often I found
myself having too much on my plate. Don’t get me wrong, it’s been so much fun
and I have loved it. But sometimes you gotta draw the line for yourself.
</p>
<p>
So next year I will be focusing on myself. I will cut the amount of events
I’ll organize down to 30 (9 meetups, a frontend conference, couple of
programming workshops and little bit of space for good ad-hoc events that I’ll
come up with during the year). This will open up over 1000 hours of extra time
that I plan to use on myself: walking, reading, playing board and video games,
writing, watching sports and meditating.
</p>
<p>
It will not be easy. Already by now, I’ve been trying to slow down for the
past week or two and I have had the urge to start new projects and plan new
big event series. So I might change my mind by the second week of January.
That’s me.
</p>
5 Reasons to Attend a Meetup
2017-09-20T00:00:00Z
https://hamatti.org/posts/5-reasons-to-attend-meetup/
<p>Meetups are fun and we are happy to see the culture is spreading all around the world making them more accessible to people. We started Turku ❤ Frontend a couple of years ago with the aim to help people learn new skills, meet fellow developers and make it easier for professional developers and students to find jobs and thus, making it easier for companies to find local talent.</p>
<p>If you have never been to a meetup, it can be bit intimidating. We wanna demystify the experience to encourage more and more people to join. And we are not alone: in Turku, there are many other fantastic meetups — something for everyone.</p>
<h2>1. Learn new skills</h2>
<p>In our meetups, the core program is two talks of frontend topics. So far, we have learned about different frameworks such as Vue.js, Angular and React as well as heard educating guides for configuring Webback and building Progressive Web Apps.</p>
<p>These 30-minute talks are a great way to learn about what’s out there and hear from fellow developers how they have tackled particular situations. The Q&A after the talk gives you an opportunity to ask clarifying questions and share your knowledge to add to the talk.</p>
<h2>2. Meet fellow developers and designers</h2>
<p>While main program is about talks, the real value is the community and an opportunity to meet with fellow developers and designers, get to know them and learn from each other.</p>
<p>In your daily life, working in a company 9–5 punching in code and then going home, you usually don’t end up communicating with developers outside your company. With meetups, hackathons and conferences, you get an opportunity to find friends and grow your network in a cozy and informal setting.</p>
<h2>3. Meet companies</h2>
<p>In addition to great people, we bridge the gap between professionals and companies. It’s a two-way street that benefits both parties. Outside meetups, how often do you get an opportunity to visit offices of the best companies in your city, drink some beer/soda and learn about what it’s like to work there?</p>
<p><a href="https://www.forbes.com/sites/susanadams/2011/06/07/networking-is-still-the-best-way-to-find-a-job-survey-says/#47a141544366">Most of the jobs in software development are found through networks</a>. Being active in the meetup scene, getting to know both developers and companies in the area and building personal relationships makes it so much easier to find new gigs.</p>
<p><img src="https://hamatti.org/assets/img/posts/5-reasons-to-attend-meetup/meetup-anders.jpeg" alt="" /></p>
<h2>4. Share your knowledge</h2>
<p>Giving a talk in a meetup is a great way to contribute to the community, practice public speaking and build personal brand. We offer an easy way to get started — give a 30-minute talk amongst 25–30 friends.</p>
<p>If you’re interested in getting to talk in bigger conferences, using a meetup as a springboard is good way to get your proposals through as you can provide some references of your previous speaking experience.</p>
<p>And let’s face it — giving back to the community feels amazing.</p>
<h2>5. Free drinks and snacks</h2>
<p>In our meetups, the hosting company offers some drinks and food/snacks for the participants. It’s a good deal: you learn new things, meet new people and get your dose of snacks for the day.</p>
<p>All in all, there are many good reasons to join a meetup. You don’t have to worry about not being good enough or not knowing what to do — we’ll help you out!</p>
<p><strong>Turku ❤ Frontend is a community of developers and designers interested in frontend web development and design. We run monthly meetups and organize other events like hackathons, programming competitions and projects like Turku Gives Back. Follow us on <a href="https://twitter.com/turkufrontend">Twitter</a>, register to join upcoming meetups in <a href="https://meetabit.com/communities/turku-3-frontend">Meetabit</a> and join the discussion on <a href="https://www.facebook.com/groups/turkufrontend/">Facebook</a> and <a href="https://turkufrontend.fly.dev/">Slack</a>.</strong></p>
Improve the world — help someone learn
2017-09-18T00:00:00Z
https://hamatti.org/posts/improve-the-world-help-someone-learn/
<p>We all have skills that other people lack. Whether it’s programming, knitting, writing, fixing bikes or cooking delicious food, you are better doing it than someone else.</p>
<p>And while everyone of us is good at something, we also crave to learn new skills to improve our lives.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">I have a rule:<br /><br />Whenever I travel for conferences or work, half my meetings must be with people new to the industry.</p>— Stephanie Hurlburt (@sehurlburt) <a href="https://twitter.com/sehurlburt/status/869060256193630208?ref_src=twsrc%5Etfw">May 29, 2017</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>In May, I was fascinated by <a href="https://twitter.com/sehurlburt/status/869060256193630208">Stephanie Hurlburt’s tweet above</a>. I engaged in a discussion with a couple of people on how to help others and was inspired to do something. Previously, I had organized workshops such as Rails Girls workshops for women and Boost Summer of Programming for non-technical co-founders but I always felt that it wasn’t enough. Few days or weeks worth of group teaching felt like I wasn’t quite able to provide enough value.</p>
<p><img src="https://hamatti.org/assets/img/posts/improve-the-world-help-someone-learn/illustration.jpeg" alt="Photo by Isaac Jenks on Unsplash" /></p>
<p>This summer, I decided to start a programming mentoring program Koodimentori (Finnish for Code Mentor) to provide targeted 1on1 help for beginners or junior-level developers. Ever since, few people have offered their help or asked if they could join the program as mentors. While I don’t want to start managing a group of mentors at this point, to you and to all others wondering, my advice is:</p>
<blockquote>
<p>Just do it!</p>
</blockquote>
<p>You don’t need fancy mentoring programs nor marketing stunts to get going. Find one individual who wants to learn and start by sitting down with them, helping them learn something. You don’t have to commit to a long-term plan nor taking X amount of people who you’ll help. Start with one person and one session and go from there.</p>
<p>Giving a few hours in a month of our time is possible for everyone. For finding people to help: just ask people around you. Post on your Facebook, Instagram, Twitter or Snapchat that you want to help. And if you feel like you are not good enough to teach, you have my permission to start. You don’t have to be the world champion to help someone get started. All you have to be is sincere and willing to help.</p>
<p><strong>What will you teach this month?</strong></p>
Intentional and multifunctional - How I got rid of most of my belongings
2017-06-30T00:00:00Z
https://hamatti.org/posts/intentional-and-multifunctional-how-i-got-rid-of-most-of-my-belongings/
<p>I have a background as a hoarder. I lived my childhood in a big suburban home and we threw away almost nothing. As I moved to smaller and smaller apartments, I started seeing the problems with the amount of my stuff. First, I imagined the problem was just bad organizing or dull and stupid floor plans. But eventually I realized the problem was my huge piles of junk.</p>
<p>My journey towards minimalism started — as I’ve noticed to be similar to many others — through looking for inspiration in tiny houses: I wanted to figure out how I could use my small apartment optimally. Luckily, instead of spending tons of money trying to build something cool, I found out about minimalism. It started all innocent by watching some Youtubers talk about their lives and soon enough I ended up watching Minimalism: A Documentary About Important Things and learning more from Joshua and Ryan through their TEDxTalks and their book.</p>
<p>Even though to my friends it might have seemed like a “get rid of everything” idealism, for me it was mostly about finding the minimal subset of belongings that made me happy and functional.</p>
<h2>Intentional</h2>
<p>First of all, I wanted to go through absolutely everything I owned (and that’s quite a lot) and decide, if each item would have a purpose. Same goes with buying new stuff. I don’t want to own stuff that just exists in a box or on a shelf somewhere without me actually interacting with it ever.</p>
<p>I started with clothing and encountered problems: I had built emotional attachments to many of my shirts and hoodies. Most of them I had never worn or received it for free from a tech conference or something. After a little struggling, I ended up donating 90% of my wardrobe to charity. I still have bit too much but at least the next round will be easier.</p>
<h2>Multifunctional</h2>
<p>I had truly reached a point where I had an abundance of stuff. I had multiple similar items in every category, especially technology — mostly because I could. I started looking into the multifunctionality of my possessions trying to find the subset that solves my problems in more than one way.</p>
<p>I unplugged my TV, sold all but two (Playstation 4 and Nintendo 3DS) of my game consoles and moved my PS4 to use my computer display. I used to have a Bluetooth speaker for the shower and a hifi system for my living room — I got rid of the hifi system. Now I only have the Bluetooth speaker (that I’m looking to upgrade) that serves as a speaker for my shower moments, Macbook usage and PS4 gaming. The last one required a few hacks but I finally found a way to make it work.</p>
<p>After I had gotten rid of my TV and consoles, I was able to get rid of a sofa and a TV stand. That freed up so much space and suddenly I didn’t need a huge living room anymore.</p>
<p>Being a single guy, I’m constantly surprised by the amount of kitchen items I own. Six large plates, six small plates, 8 forks/knives/spoons, bowls of all sizes. I ended up getting Monbento lunch box which now acts as my only dish to eat meals from, ice cream bowl and lunch box so I always can have food to go with me.</p>
<h2>YAGNI</h2>
<p>As a software developer, I’m intimately familiar with YAGNI thinking. It stands for <a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it">You aren’t gonna need it</a> and means that you shouldn’t optimize your code for stuff that might be required in the future, as you won’t know what is needed anyway and you’re just wasting your time.</p>
<p>Same goes with a lot of stuff. “Maybe one day…” — no, it won’t happen. If you haven’t needed it for a year, two years or — as I found out with some of my oldest stuff — ten years, you’re probably never gonna need it. Get one if you happen to need it in ten years instead of carrying it with you all the time.</p>
<p>Of course this can be applied to the extreme as well and fail horribly. During the summer, you shouldn’t get rid of your winter clothes but store them somewhere deliberately.</p>
<p><img src="https://hamatti.org/assets/img/posts/intentional-and-multifunctional-how-i-got-rid-of-most-of-my-belongings/kindle.jpeg" alt="" /></p>
<h2>Digitize</h2>
<p>I had five huge moving boxes full of books, DVDs, notebooks and video games. I also have a Kindle, Netflix subscription, Evernote and Playstation/Steam/Nintendo 3DS that all are digital. So I got rid of my books, DVDs and games and went full digital with my entertainment.</p>
<p>I love physical books and I love the idea of a DVD. However, I found out that I actually never read those physical books nor watched those DVDs — I would always find a digital version and consume them.</p>
<p>And books are heavy. My friends can heave a sigh of relief when they don’t have to help me carry that stuff anymore.</p>
<p>I’m still a notebook junkie. I do use Evernote more and more but for me, pen and paper is the best interface into my brain. And I love being able to make notes to a notebook when interacting with people instead of hiding behind my computer screen. So I kept the notebooks but I’m planning on digitizing them once they are full so I only need to have one at a time.</p>
<h2>I Have Just Started</h2>
<p>I still haven’t gotten rid of every excess thing I own but with every passing week I’m making good progress. Selling, donating and recycling stuff takes a surprising amount of work but I still have two more months before I become apartmentless/between apartments for a month. Before that, I want to figure out what I absolutely need and want to own so that I can figure out which apartment I should live in next. Instead of making 2.5 van trips to move all my stuff, I want to fit everything except furniture into a sedan. And I’m getting closer to that every week.</p>
<p>One of the struggles I haven’t yet solved is <strong>getting gifts</strong>. It feels so horrible to throw away something you got as a gift and it’s difficult to tell everyone that you don’t want any physical things as a gift since they mostly only declutter the space.</p>
Code in the Dark Turku
2017-05-24T00:00:00Z
https://hamatti.org/posts/code-in-the-dark-turku/
<p><em>What happens when you put 20 developers behind computers, give them nothing but an editor and a reference screenshot? Together with <a href="https://valohai.com/">Valohai</a> and <a href="http://hubturku.com/">Hub Turku</a> and supported by <a href="https://www.reaktor.com/">Reaktor</a> we organized the first Code in the Dark event in Turku last night and it was a blast.</em></p>
<p><a href="http://codeinthedark.com/">Code in the Dark</a> is an event where developers team up to replicate a given website in 15 minutes using nothing but the editor. You are not allowed to preview what you’re building, you are not allowed to look for docs — nothing. Just type code, have fun and do your best.</p>
<p>We had 7 teams of 1–3 people varying from high school students and university CS students to experienced front end developers and designers. The beauty of the Code in the Dark format is that you are not going to succeed no matter how good you are. So it’s fun regardless and small mistakes turn into really funny broken websites in the end.</p>
<p>In the round 1 teams had to replicate an iconic view of the Internet: <a href="https://google.com/">the Google home page</a>. As some teams didn’t realize that the image assets were provided for the page, we saw some minimalistic black-and-white versions as well as CSS styled letter-by-letter colored Google logos.</p>
<p>After each round all the participants: teams and spectators get to vote for the best solution and the first round proved to be super tight. Joni & Juho from Taiste and Kari, Eero & Viktor from <a href="https://vaadin.com/home">Vaadin</a> tied for the first place. As each team gets to justify their design choices, we started seeing bantering between the teams about small details they got correct while others missed.</p>
<p><img src="https://hamatti.org/assets/img/posts/code-in-the-dark-turku/round1.jpeg" alt="" /></p>
<p>For the second round the stakes were raised. Teams had to create the front page of Hub Turku — a co-working and makerspace where the event was held. This time we learned that mixing up padding and margin can make a huge difference in how the website looks like. Teams’ solutions also varied heavily on the size and usage of the image provided: some went full-on and filled the screen with the beautiful picture while others took a more minimalistic route.</p>
<p>The winner of the second round was the high school team of three. The young talent showed that web development skills are not purely correlated with age and experience.</p>
<p><img src="https://hamatti.org/assets/img/posts/code-in-the-dark-turku/round2.jpeg" alt="" /></p>
<p>Finally, the third and final round was the real test of skill. Teams had to replicate the Twitter page of president Trump. Lots of different elements to figure out and style correctly and only 15 minutes to spare. This is where Taiste guys showed why they are so good. They won the last round with a landslide as they were able to create beautiful replica of the site.</p>
<p>Code in the Dark Turku was a successful event where people had a lot of fun while learning new stuff. We want to thank Valohai for organizing the event with us, Reaktor for sponsoring the drinks and snacks and Hub Turku for offering the space. And most importantly all the people who participated either as developers or as the audience.</p>
<p><img src="https://hamatti.org/assets/img/posts/code-in-the-dark-turku/round3.jpeg" alt="" /></p>
<p><em>Turku ❤ Frontend is a community of developers and designers interested in frontend web development and design. We run monthly meetups and organize other events like hackathons, programming competitions and projects like Turku Gives Back. Follow us on <a href="https://twitter.com/turkufrontend">Twitter</a>, register to join upcoming meetups in <a href="https://meetabit.com/communities/turku-3-frontend">Meetabit</a> and join the discussion on <a href="https://www.facebook.com/groups/turkufrontend/">Facebook</a> and <a href="https://turkufrontend.fly.dev/">Slack</a>.</em></p>
<p><em>Valohai is a startup building a new home for machine learning. Run, monitor, store and share all your experiments. Follow them on <a href="https://twitter.com/valohaiai">Twitter</a> and check <a href="https://valohai.com/">their website</a> for more!</em></p>
<p><em>Hub Turku is a co-working place and makerspace located downtown Turku. It’s open for everyone to work, experiment and host events like these. Follow also them on <a href="https://twitter.com/hubturku">Twitter</a> and find out more at <a href="http://hubturku.com/site/en/co-working-in-the-heart-of-the-city/">their website</a>.</em></p>
<p><em><a href="https://www.reaktor.com/">Reaktor</a> is a strategy, design, and engineering company based in New York, Helsinki, Amsterdam, and Tokyo.</em></p>
If you are not doing code reviews, start now!
2017-05-04T00:00:00Z
https://hamatti.org/posts/if-you-are-not-doing-code-reviews-start-now/
<p>I was introduced to code reviews few years back in my first startup job and as a junior developer I immediately felt their impact. Having my code reviewed by seniors and reviewing code myself taught me a ton. After that job, I switched to another startup and we had a great code review process in place as well. I thought world was a good place.</p>
<p>Today a friend of mine complained about a project she inherited. It was thousands of lines of unintended code with whole functions commented out. Another friend told his horror stories from work and I felt baffled: <em>“How is it possible that code like this goes through to production?”</em>.</p>
<p>Apparently many of my friends work as software developers in companies that have no code reviews set up. Everyone does what they wish and push it to production. And that’s really scary.</p>
<p><img src="https://hamatti.org/assets/img/posts/if-you-are-not-doing-code-reviews-start-now/laptop.jpeg" alt="" /></p>
<h2>Code reviews help ensure quality</h2>
<p>Code reviews combined with automated tests and style checks helps a lot when battling with the code quality. Having a second pair of eyes look at every piece of code that goes to production helps find out brain farts and weird choices: we all make silly, easy-to-spot mistakes when we are too deep in our own code.</p>
<p>Many people complain that code reviews become fights over small issues: code styling, variable naming etc but big part of that can be avoided by using automatic linters as part of the CI process. Something like <a href="http://eslint.org/">ESLint</a> for Javascript and <a href="https://www.python.org/dev/peps/pep-0008/">PEP8</a> for Python are easy to set up and they help you guarantee that the code remains consistent.</p>
<p>I don’t personally give a damn if we use 2 spaces, 4 spaces or tabs or if we put the opening curly brackets after <code>if</code> or on the next line. What I do care about, is consistency. With small adjustments in automated processes (f. ex. break a build if the style guide is violated) you can help the team in the future. New people can easily grasp the code and architecture when everything looks the same and is named logically.</p>
<p>Code review doesn’t have to slow down the development. It doesn’t have to be a rigorous all-hands meeting for every pull request but having one other team member go through your code, give feedback and ask questions. The small extra time used will be saved in the future with less bugs, less maintenance and faster on-boarding with new employees or people changing projects.</p>
<p><img src="https://hamatti.org/assets/img/posts/if-you-are-not-doing-code-reviews-start-now/review.jpeg" alt="" /></p>
<h2>It’s about the shared code, not you</h2>
<p>Giving and receiving feedback is difficult. I absolutely hate when someone criticizes my work but we all need to learn how to deal with that. Every piece of code you contribute to your team is <strong>a shared responsibility</strong>, it’s not your code. That’s why it’s important that the code that goes in and stays in for months and years, isn’t just any random quirks that someone came up with.</p>
<p>Even with code reviews, automated tests and style checking, you cannot completely avoid bugs and problems. But it will reduce the amount of them and it will give your team better view on what could be the cause for a bug: when more people have seen the code beforehand, more people have an opportunity to recognize the issues.</p>
<h2>Code buddies for freelancers</h2>
<p>If you work alone, it can be difficult to do code reviews. Consider finding a trusted fellow freelancer with whom you can do <em>quid pro quo</em> code reviewing. Reviewing an unknown code base is much more difficult but at least if you work on the same technology (say, both of you do Django), you can find some pitfalls from each other’s code.</p>
Why do good communities thrive?
2017-04-21T00:00:00Z
https://hamatti.org/posts/why-do-good-communities-thrive/
<p><em>Quick note of terms used: I use the term professional common and professional to refer to whatever topic your community revolves around. It does not have to be professional as in work or business.</em></p>
<p>Recently I’ve been talking a lot with different people about communities, community management and different aspects related to it. One of the key differences in how people view communities is what I call professional-leisure divide. (I hope one day I’ll have a better word for it.)</p>
<p>Pretty much everyone is on the same page when we talk about professional common. People join a community for the core reason that community exists: whether it is being a fan of a certain sports team, entrepreneurship, learning new technology, the passion for gardening or just living in the same location.</p>
<p>If a community does not have that focus, it most probably will not live or even really get started. Most of your activities in the community revolve around this: watching the game together, doing workshops or sharing tips of the best ways to grow turnips.</p>
<p>However, my observation is that the crucial part of a thriving and successful community has to do with leisure common. Members of the community coming together to do non-professional things together. Connection between people can be started while doing the professional thing but they develop when doing things that you do in your normal life: going out for dinner, playing games, watching movies, going for a hike — whatever the people in your community love to do.</p>
<p>It also helps people to be more relaxed, more efficient and more productive in their communities because usually being with people outside the professional setting lowers tension and helps you avoid assumptions and misunderstandings in communication.</p>
<p>Organizing leisure common activities can easily seem as waste of resources but neglecting it hurts in a long-term. I’ve been a member in communities that have lived long beyond the end of the professional common. That’s a success in my books. Because communities are first and foremost about people, not things.</p>
Build your community like a garden
2016-12-22T00:00:00Z
https://hamatti.org/posts/build-your-community-like-a-garden/
<p><em>All illustrations in this blog post are by the wonderful <a href="https://jennywiik.wordpress.com/">Jenny Wiik</a></em></p>
<p>I have recently found interesting parallels in building and managing communities and gardening. Understanding those parallels and learning from the ancient art of gardening can provide tools for effective community management.</p>
<p><img src="https://hamatti.org/assets/img/posts/build-your-community-like-a-garden/soil.jpeg" alt="" /></p>
<h2>1. The soil</h2>
<p>Just like a gardener, a community manager needs make sure that the ground is perfect for growing. Provide an <strong>environment</strong> with opportunities and tools for the community members to experiment, socialize and grow. If the environment is toxic, dry or boring, you end up with people not forming an active community. With the right environment, the community will build itself.</p>
<p>What the soil means to a specific community obviously depends on the community. Sometimes it’s a physical place: a building, a co-working space, a sports facility. Sometimes it’s an online platform: Slack, discussion forum, Facebook group, Twitter hashtag.</p>
<p>The most important thing is to find something that works for your community. The context, what people are used to and the goal are all variables that define the right environment.</p>
<p><img src="https://hamatti.org/assets/img/posts/build-your-community-like-a-garden/mix-match.jpeg" alt="" /></p>
<h2>2. Avoid mix-and-match</h2>
<p>Don’t put plants that need different kind of soil and vitalizers into the same space. This does not, by any means, mean the lack of diversity. It means that people inside a community come to the community for a common reason.</p>
<p>Figure out, what is the common denominator in your community and attract people who share that. If you fail to do that, you will end up trying to attract everyone (usually of the fear of not attracting enough) and that results in not having active core members who feel that it is their community.</p>
<blockquote>
<p>If nobody feels your community as their own, there will be no community.</p>
</blockquote>
<p><img src="https://hamatti.org/assets/img/posts/build-your-community-like-a-garden/grow.jpeg" alt="" /></p>
<h2>3. Let it grow</h2>
<p>Gardener doesn’t grow plants. Gardener’s job is merely to make sure that the circumstances for growth are in place. Similarly, you don’t build a community by telling people that they need to be a community.</p>
<p>One key factor to take into account is that a vibrant and active community builds itself: the members of the community create the community, not the manager. Often this means that some elements of the community go to a different direction than you might plan, mean or want. Often it’s best just to let it happen organically. However, sometimes you need to refocus or redirect the path to match your goal.</p>
<p>It’s important though, that you are not too strict and dominating. That kills the organicity, stops growth and mostly just gets people annoyed. If you tell the community to stop an action they as a group enjoy (even if it feels stupid to you), you are effectively killing the community. <em>(Unless it’s something bad: there’s no reason to allow racist, sexist, etc actions in your community. It’s not an all-goes principle, be a good human.)</em></p>
<p>The community should always exist for the purpose of the community and people in it. If your community is vibrant and active, it will serve the other purposes you want to have a community for.</p>
<p><img src="https://hamatti.org/assets/img/posts/build-your-community-like-a-garden/light.jpeg" alt="" /></p>
<h2>4. Let ’em get light</h2>
<p>Whatever your reason to start a community is, you usually want to have people talking about the community (and indirectly, your product/company/purpose). And one thing people love talking about is themselves and what they have done.</p>
<p>So don’t keep your community members in the shadows. Make the community about them. Bring them up into the limelight and showcase to the rest of the community and outside the community the great efforts they make. A blog post, tweet or Facebook post about a community member or a project they’ve made or something they participated in gives them something to share to their networks. And believe me — they will.</p>
<p>A great community manager helps individual community members to become better or enjoy their time more. Show that you understand that the community is all about them — not you or your organization or company. Don’t just restrict it to showcasing things that are done to benefit your community but anything they value.</p>
<p><img src="https://hamatti.org/assets/img/posts/build-your-community-like-a-garden/enjoy.jpeg" alt="" /></p>
<h2>5. Enjoy your garden</h2>
<p>For me, being a community manager (if you didn’t already figure out of my horrible gardening examples, not a gardener) brings vast amount of joy from seeing members of our community grow and be successful.</p>
<p>Enjoy the achievements and successes with your community members. That’s what makes community management so special.</p>
<blockquote>
<p>And remember: it’s not about you, your company nor your product. It’s about the people.</p>
</blockquote>
<p>Thanks to amazing Jenny Wiik for the illustrations, check out <a href="https://jennywiik.wordpress.com/">her other work</a> too!</p>
What Rails Girls taught me about non-organizations
2016-12-19T00:00:00Z
https://hamatti.org/posts/what-rails-girls-taught-me-about-non-organizations/
<p>Finland is the promised land of organizations. Almost every citizen is or has been part of a non-profit organization during their life and most of us have been a board member in one or two or … well, let’s just say some of us do it a lot.</p>
<p>As I grew up here and was for the majority of my life a conservative by-the-book guy, everytime I planned of starting something new, I immediately thought about starting an organization, establishing a board, organizing a founding meeting and monthly board meetings and thinking about the paperwork. Long story short, I didn’t start a lot of things.</p>
<p>I had been a secret fan of <a href="http://railsgirls.com/">Rails Girls movement</a> for quite a while before I gathered enough courage to email local organizers in Helsinki. For a long time I felt that since I wasn’t a professional developer and not a Ruby on Rails guru by any means, I never asked if I could join. Lucky for me, in the fall of 2013 I found that courage, emailed Lotta who was doing one in Helsinki Nov 2013 and she replied and took me in.</p>
<p><img src="https://hamatti.org/assets/img/posts/what-rails-girls-taught-me-about-non-organizations/railsgirls.jpeg" alt="Rails Girls Helsinki, November 2013 — Photo by Yu Shen" /></p>
<p>After that one slushy weekend in Helsinki, I’ve coached and/or organized bunch of Rails Girls workshops all around: San Francisco, Helsinki, Turku, Jyväskylä, Salo and helped a bit on the side with the global community. One of the magical things that happened though was what I learned.</p>
<p><img src="https://hamatti.org/assets/img/posts/what-rails-girls-taught-me-about-non-organizations/rg-turku.jpeg" alt="Rails Girls Turku, March 2015" /></p>
<p>Rails Girls local chapters are not registered organizations. They are just groups of (let me say, amazing) people who gather together and spend their time helping women learn programming all around the world. Organizing these workshops got me comfortable with doing stuff without starting a formal organization.</p>
<p>When I started <a href="https://turkufrontend.fi/">Turku ♥ Frontend</a> in December 2015, I was already in a situation where starting an organization felt like a distraction. I didn’t want any of that board meeting nonsense. We become a community instead of an organization. We don’t have members, we don’t have a board and (as an obvious consequence) we don’t deal with money. We look out for local companies who can sponsor us directly by buying the beers and snacks for the events. Sometimes it sucks that we can’t take sponsorship money but as money usually complicates things a lot, I’m more than happy to make that trade-off.</p>
<p><img src="https://hamatti.org/assets/img/posts/what-rails-girls-taught-me-about-non-organizations/turkufrontend.jpeg" alt="Turku ❤ Frontend meetup in March 2016" /></p>
<p>The most important change has happened in my mindset. Instead of deciding to do something and creating structure around it, I just do it. If the structure one day becomes inevitable, then I cherish it. But until that day, I’ll just keep doing it. Today I realized that I never actually made a decision to start a meetup community. I just started a Facebook group on a whim and that group grew virally to become a meetup community. The story of how that happened can be <a href="https://hamatti.org/posts/building-a-developer-community-calls-for-strong-support">read here</a>.</p>
<p>I have the luxury of having a full-time job where my main function is to help others do the same. To start building things: businesses, non-profits, their life, anything. And anywhere I go, I find that I’m surrounded by amazing communities of people willing to chip in with their time, knowledge, expertise — or sometimes — money. And I love it.</p>
Building a developer community calls for strong support
2016-12-13T00:00:00Z
https://hamatti.org/posts/building-a-developer-community-calls-for-strong-support/
<p>Almost everyone who’s been in touch with crowdfunding in a form or another or has seen a talk about it, knows the classic 4 F’s. Friends, Family, Fools and Fans. I’ve learned that in some sense, it applies to so much more than just crowdfunding.</p>
<p>When I talk about fools here, I don’t mean idiots or stupid people. I mean those crazy brave people who support you before anyone else. The ones who believe in you even before you really do. And those people — I’ve learned — are the most important ones when you start something new.</p>
<p>Our fools are the wonderful people from a local company <a href="https://www.poutapilvi.fi/">Poutapilvi</a> (sorry for calling you fools). When we started the meetup community a year ago, we went through our LinkedIn connections and local company listings and researched the Twitter accounts of all the software houses in Turku. Before contacting anyone, we tweeted out a simple tweet.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Turku <3 Front end is a new meetup group for everyone who's into <a href="https://twitter.com/hashtag/frontend?src=hash&ref_src=twsrc%5Etfw">#frontend</a> development in <a href="https://twitter.com/hashtag/turku?src=hash&ref_src=twsrc%5Etfw">#turku</a>. Let's get started<a href="https://t.co/rpeEeJTjTh">https://t.co/rpeEeJTjTh</a></p>— Juhis ❤️ (@Hamatti) <a href="https://twitter.com/Hamatti/status/674581030658031616?ref_src=twsrc%5Etfw">December 9, 2015</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>After that we pinged all those companies we found and asked for support. Something amazing happened. These amazing companies like Poutapilvi tweeted back: we are in. And others followed. We had a community. Within the first 48 hours we had 0ver 100 developers in the Facebook group, couple of companies on board to sponsor and we were caught by a surprise — there was a real demand to be filled.</p>
<p><img src="https://hamatti.org/assets/img/posts/building-developer-community-support/afterbeers.jpeg" alt="" /></p>
<p>Tomorrow is the 9th meetup of our first year. During the year we’ve had (tomorrow included) these 9 meetups with 18 absolutely fascinating talks, one hackathon and lots of new connections made. Today we did a quick tour sharing the love and thanking our partners for their bravery and trust.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Today we did a lil' <a href="https://twitter.com/hashtag/Christmas?src=hash&ref_src=twsrc%5Etfw">#Christmas</a> tour thanking all the great partners we had this year. <a href="https://twitter.com/sofokus?ref_src=twsrc%5Etfw">@sofokus</a> got the last box of chocolate. <a href="https://twitter.com/hashtag/meetup?src=hash&ref_src=twsrc%5Etfw">#meetup</a> <a href="https://t.co/h7A0AP8SJa">pic.twitter.com/h7A0AP8SJa</a></p>— Turku ♥ Frontend (@turkufrontend) <a href="https://twitter.com/turkufrontend/status/808599024957788160?ref_src=twsrc%5Etfw">December 13, 2016</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>With Christmas coming, nothing was more appropriate than a box of chocolates and lovely Hello Ruby Christmas cards.</p>
<p>None of us in the organizing team had any experience in running a meetup. We took a stab into the unknown because we felt that something like this was missing from the local landscape. Today we are 241 people strong community with regular monthly meetups that “sell” out (no cost but registration needed) in just a few hours after announced.</p>
<p>So thank you friends, family, fools and fans. Thank you <a href="https://www.poutapilvi.fi/">Poutapilvi</a>, <a href="https://www.andersinnovations.com/en/">Anders</a>, <a href="https://www.sofokus.com/">Sofokus</a>, <a href="https://vaadin.com/home">Vaadin</a>, <a href="http://www.atrsoft.com/">ATR Soft</a>, <a href="https://www.lyyti.com/fi/">Lyyti</a>, <a href="http://www.walkbase.com/">Walkbase</a> and <a href="http://www.taiste.fi/en/">Taiste</a>. If you are ever looking for a development job in Turku, these are the companies to start with. Because they believe in developers, continuous learning and they support the community.</p>
<p><em>Turku ❤ Frontend is a community of developers and designers interested in frontend web development and design. We run monthly meetups and organize other events like hackathons, programming competitions and projects like Turku Gives Back. Follow us on <a href="https://twitter.com/turkufrontend">Twitter</a>, register to join upcoming meetups in <a href="https://meetabit.com/communities/turku-3-frontend">Meetabit</a> and join the discussion on <a href="https://www.facebook.com/groups/turkufrontend/">Facebook</a> and <a href="https://turkufrontend.fly.dev/">Slack</a>.</em></p>
Year in Review 2016
2016-12-11T00:00:00Z
https://hamatti.org/posts/year-in-review-2016/
<p>
After coming home from the Silicon Valley in the Christmas of 2014, I had a
year of soul searching and wandering about. I had no idea what I wanted to do
after I reached my big dream years before I had planned.
</p>
<p>
I spent three months doing a small freelancer gig but mostly hanging out at
the engineering guild room at university and taking a breather. After that I
moved to Helsinki and started working as a software developer at
<a href="http://smartly.io/" rel="noopener nofollow">Smartly.io</a>. I loved
the team and learned a lot but didn’t really feel like Facebook marketing
tools was something that gave me enough motivation. So after just four months
I left the company and started looking again. One more freelance gig that went
south. Then a new opening at a local student entpreneurship community opened.
I went from full-stack developer to community management.
</p>
<p>
For the whole year 2015 I felt that I wasn’t able to achieve anything nor find
any direction to my life. In December, almost year ago to the date I asked in
my Facebook feed if anyone else felt that Turku, my hometown, needed more tech
meetups. I ended up starting one after a few interested people showed support.
Now, a year later we are gonna have our 9th meetup with one hackathon
organized during the summer break and over 200 people in the community. And we
just closed sponsorships for the first three months of 2017 for our meetup
group
<a href="https://twitter.com/turkufrontend" rel="noopener nofollow">Turku ❤ Frontend</a>.
</p>
<p>
Work also became a big focus again. At
<a href="http://boostturku.com/" rel="noopener nofollow">Boost Turku</a> I was
able do a lot of things I felt were important: I ran a three-month programming
school for non-technical students, co-organized a 10-week accelerator program
for early stage companies, worked in two amazing events —
<a href="http://theshift.fi/" rel="noopener nofollow">The Shift</a> and
<a href="http://www.slush.org/" rel="noopener nofollow">Slush</a> — and was
able to inspire and motivate a bunch of people. I ran two 8-week workshop
series we called Dropout Academy with topics ranging from project management
to website building and crowdfunding. And I finally took some holidays to
chill out.
</p>
<p>
Outside work I organized programming workshops, played hundreds of hours of
board games, organized hackathons and get-togethers, programmed couple of web
apps and did social media management for a bunch of non-profits.
</p>
<p>
Now that 2016 is coming to the end, it’s time to take a few days off and plan
the future. Next year is gonna be a big and important one once again. After
August my current gig ends and I’m looking into the world of opportunities
again. I still have no idea where to focus on and where in the world I want to
work in.
</p>
<p>
One thing is for sure.
<strong><strong>2017 is gonna be even better.</strong></strong>
</p>
8 hours later + Turku <3 Frontend Hackathon
2016-08-20T00:00:00Z
https://hamatti.org/posts/8-hours-later-turku-frontend-hackathon/
<p><a href="https://turkufrontend.fi/">Turku ❤ Frontend</a> is a community of (mostly frontend) developers in Turku region that was started in December 2015. Between September and May, we organize meetups at local companies’ offices with talks and beers and during the summer break we just finished our first hackathon.</p>
<p>Bringing local developers together for a sunny summer day in August was the perfect start for my holiday. We decided to have this year’s theme as <strong>location</strong> as it’s the hot topic right now with the blooming success of Pokemon Go and IoT. We set the timer to 8 hours and started hacking – some with teams and others alone.</p>
<p>It fascinates me how amazing things people build in such a short time. A “regular” length hackathon of 48 hours is a race against the clock and the human limitations of staying awake and productive. With only 8 hours on the clock, one needs to cut every corner and simplify over and over again to be able to build the prototype for the demo session.</p>
<p>Today we saw a web application to benchmark different neighborhoods based on the statistics such as median income, average age and unemployment rate; a location-based King of the Castle inspired by Pokemon Go; a map to display Instagram videos near your location; location-based messaging system where you can leave pings and messages to others; and my solution, Lähipubiapp (Finnish for near pub app), which displays directions to the closest pub for the moments when you get thirsty.</p>
<p><img src="https://hamatti.org/assets/img/posts/8-hours-later/names.jpg" alt="" /></p>
<p>The most important was that everyone seemed to enjoy themselves. We had new people in the community attending our events for the first time as well as first-time hackathon participants learning and experimenting.</p>
<p>For the members of the community reading this: the fall is coming. We’re currently working on closing the deal with first events in September and October and you can expect to learn about frontend testing, VR/AR interfaces and mobile frontend development. Exciting times!</p>
Why I love hackathons
2015-12-08T00:00:00Z
https://hamatti.org/posts/why-i-love-hackathons/
<p>I’m a huge fan of hackathons. Last weekend, I had the privilege to organize one for the first time after attending a few. Even though being super tired on Sunday evening after a long weekend of very little sleep on a couch, I was so happy being able to organize a successful event.</p>
<p>Here are my top reasons for loving hackathons.</p>
<h2>You get to write bad code</h2>
<p>Hackathons have such limited time to come up with an idea, learning the tools and implementing the idea that there’s no way you can write perfect code with all the corner cases thought through and all different user actions taken into consideration. Once you realize that, it’s really liberating feeling. You can mock some parts of the app or only make it work on a subset of possible inputs. You can also not worry about the maintainability of the codebase – often you’ll never touch the project anyway or if it’s really good, you are probably better off rewriting it from the scratch anyway.</p>
<h2>You get to experiment</h2>
<p>In addition to writing bad code, you get to take a sidestep from your daily life. I usually try to select a new technology, framework or language to hack with during a hackathon. During our company hackathon at Chartio I created an online clone of Jeopardy TV show using Node.js since I used Python for my daily work.</p>
<p>The code base is absolutely horrible, it doesn’t always work and it lacks a few key features from the show but for the two days and the demo, I absolutely loved working on it and getting my friends playing it.</p>
<h2>You get to meet amazing people</h2>
<p>Meeting new people, teaming up with them and learning from them is a killer in hackathons. Let’s say you’re a backend developer and you team up with a UX designer and a mobile app developer. If you do things the right way, you’ll end up learning a lot about UX design and mobile development in the process. You might meet your future co-founders or find new people to your existing team. Working with someone intensively for 48 or 72 hours reveals character in people so you’ll have a way better idea about who someone is.</p>
<p>It’s often tempting to participate in a hackathon with an existing team but keep in mind the positive opportunities that arise from going in blind and finding the team at the event.</p>
<h2>You always fit in</h2>
<p>Hackathons are great because they are accessible to developers and designers of all skill levels. You can easily adjust the difficulty of your project to match your skills (it’s always better to do a project that is slightly over your current level). Both a junior and a senior developer can attend the same hackathon and they both feel bit lost for a moment in the beginning but learn something cool by the end.</p>
<h2>You get to showcase what you did</h2>
<p>Public speaking is the biggest phobia in the US. Research says that even one in four people are afraid of speaking to a crowd. I feel hackathons are a great place to practice. You’ll be talking about something you’re passionate about (I hope at least, you just spent a weekend working on it) to a group of peers in the same situation than you. You have most likely even talked with most of the people (depending on the size of the hackathon) during the weekend so you’re not even talking to a group of strangers.</p>
<p>You only learn pitching by doing it so it’s better to start as soon as possible. Good sales pitch is not a thing for only startup entrepreneurs. Everyone benefits from being able to sell their ideas, products or skills in life. The more you do it, the better you will become and at one point you’ll realize that you’re comfortable pitching anything to anyone – and you get people to buy your story.</p>
<p><strong>Write bad code, experiment, meet people, learn and showcase. That’s why I love hackathons.</strong></p>
What's with all these sorts, PHP?
2015-04-28T00:00:00Z
https://hamatti.org/posts/whats-with-all-these-sorts-php/
<p>A few days back I was ranting to my friends about PHP arrays. I’m not as much of a PHP hater as many but there’s still some things that really bug me. Everything to do with arrays is one of them. Let’s have a look. I use the phrase normal array to mean a non-associative array (like Python’s list or Java’s array).</p>
<h2>Associative ordered arrays</h2>
<p>As the <a href="http://php.net/manual/en/language.types.array.php">documentation</a> states, <em>“An array in PHP is actually an ordered map. A map is a type that associates values to keys. This type is optimized for several different uses; it can be treated as an array, list (vector), hash table (an implementation of a map), dictionary, collection, stack, queue, and probably more.”</em></p>
<p>I think it’s doing way too much and is way too confusing. You can kinda use it as dozen of different things but not really. If you just define <code>$arr = array(1,2,3)</code>, it acts like a normal array (what you would call as a list in python). But only as long as you don’t delete anything. <code>unset($arr[1])</code> removes the second value (2 in this case) and it stops working as a normal array.</p>
<pre class="language-php"><code class="language-php">$arr = array(1,2,3);
var_dump($arr, json_encode($arr));
unset($arr[1]);
var_dump($arr, json_encode($arr));</code></pre>
<p>The first one encodes into json array <code>[1,2,3]</code> and the latter one into JSON object <code>{"0": 1, "2": 3}</code>. So instead of numeral, continuous indices, it converts into associative-only array with two string keys. At least for me it has caused so much confusion when dynamically manipulating these arrays.</p>
<h2>But what about sorting?</h2>
<p>The PHP standard library is well known for having inconsistent naming and parameter order for functions that do same kind of things. For sorting, there are <a href="http://php.net/manual/en/array.sorting.php">dozen functions</a>, named consistently but really bad. There’s of course <code>sort</code>. Instead of giving a flag or parameter to sort to reverse the order, you have <code>rsort</code>. Then if you want to keep the key=>value pairs intact, there’s <code>asort</code> (which has caused me most confusions ever since I accidentally used that one instead of sort) and <code>arsort</code>. Then you can sort with keys <code>ksort</code> and reverse <code>krsort</code>. For natural sorting there’s <code>natsort</code> and case insensitive <code>natcasesort</code>. And when all this is not enough, you can use custom defined comparing function with <code>usort</code>, <code>uasort</code> and <code>uksort</code>. And the inconsistently named <code>array_multisort</code> for multidimensional arrays.</p>
<p>Some of the effects you can achieve by using flags with different sorts and for some things, you must use flags since there are no aliases for everything.</p>
<p>I have to admit, I’ve burnt myself too many times with sorting issues on PHP by using the wrong function. Especially when I was just beginning PHP years ago, I just googled and stackoverflowed for answers and would end up with different answers. So I used <code>asort</code> in one place and <code>usort</code> elsewhere and didn’t really know the difference.</p>
<p>Too many sorts</p>
<p>I just think there are too many ways to do one thing. It’s confusing for beginners, it is redundant for skilled developers and the naming scheme is – even if consistent – pretty horrible. I am the kind of developer who values ease of writing code over everything else. I don’t really care about efficiency (until it’s absolutely necessary) nor even the benefits of strong static typing. I just want to write some code and enjoy the process.</p>
Beauty of the Pipe
2014-09-09T00:00:00Z
https://hamatti.org/posts/beauty-of-the-pipe/
<p>The Unix Pipeline is a powerful and beautiful piece of software that is sometimes difficult to grasp for a command line beginner. We are used to use graphical interface apps that mostly only interact with each other by writing and reading files if at all. The concept of standard out (stdout) and standard in (stdin) are something that takes some time to learn and understand when one is learning programming and/or data tools in command line.</p>
<p>The examples expect some level of understanding on the basics of terminal, such as parameters and flags. Some of the commands can take file as a parameter and as such, don’t require cat but for the sake of education, I will not go through that route.</p>
<p>Let’s start with basics. cat is a command that outputs the contents of the file into standard out. So running</p>
<p><code>$ cat file.txt</code></p>
<p>will print the entire contents. If we want to get 10 first lines, we pipe the results of cat into head command. The pipe, |, will feed the standard output of one command as standard in for another.</p>
<p><code>$ cat file.txt | head -n 10</code></p>
<p><strong>Print out top 10 words by word count in file file.txt with the frequencies</strong> is one exercise that is usually thrown at students in basic courses. Let’s build a pipe for this exercise one step at a time.</p>
<ol>
<li>Let’s start with the simplest, outputting the file to stdout</li>
</ol>
<p><code>$ cat file.txt</code></p>
<ol start="2">
<li>Next, we need to tokenize the output to contain one word per line. For that purpose, tr is useful command. It will replace all spaces with line breaks.</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n'</code></p>
<ol start="3">
<li>Next, we need to do little cleaning up. We want to remove all punctuation, commas, periods, exclamation points etc. Let’s introduce sed, the stream editor. We will use simple regex to help us out here.</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n' | sed 's/[.-\!?,]//g'</code></p>
<ol start="4">
<li>Next, we want to lowercase everything, so that ‘Lorem’ and ‘lorem’ count for the same word.</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n' | sed 's/[.-\!?,]//g' | tr '[:upper:]' '[:lower:]'</code></p>
<ol start="5">
<li>There is a command uniq which combined with -c flag gives us the count of words. However, for that to work, we need to sort the lines first since it only works on sequential lines.</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n' | sed 's/[.-\!?,]//g' | tr '[:upper:]' '[:lower:]' | sort</code></p>
<ol start="6">
<li>Then, let’s apply uniq -c</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n' | sed 's/[.-\!?,]//g' | tr '[:upper:]' '[:lower:]' | sort | uniq -c</code></p>
<ol start="7">
<li>To get the ten most used words, we need to sort again, this time in reverse order</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n' | sed 's/[.-\!?,]//g' | tr '[:upper:]' '[:lower:]' | sort | uniq -c | sort -r</code></p>
<ol start="8">
<li>And finally, use head to get first ten lines</li>
</ol>
<p><code>$ cat file.txt | tr ' ' '\n' | sed 's/[.-\!?,]//g' | tr '[:upper:]' '[:lower:]' | sort | uniq -c | head -n 10</code></p>
<p>Just looking at the final command pipe can seem really intimidating but it is important to remember, that the beauty of the pipe is the fact that you can build it one step at the time and always see what is going on between every step.</p>
<p>With the great variety of command line tools for data manipulation, it is possible to do complex things like scraping data from HTML table in Wikipedia into a JSON file that only contains wanted columns:</p>
<p><code>curl -s 'http://en.wikipedia.org/wiki/List_of_countries_and_territories_by_border/area_ratio' | scrape -be 'table.wikitable > tr:not(:first-child)' | xml2json | jq -c '.html.body.tr[] | {country: .td[1][], border: .td[2][], surface: .td[3][], ratio: .td[4][]}' | head</code></p>
<p>Above example is from the great blog post <a href="http://jeroenjanssens.com/2013/09/19/seven-command-line-tools-for-data-science.html">7 command-line tools for data science</a> by Jeroen Janssens.</p>
Case study - DataMonkey as educational platform
2014-08-09T00:00:00Z
https://hamatti.org/posts/case-study-datamonkey-as-educational-platform/
<p>A coworker of mine shared a link to <a href="http://datamonkey.pro/">DataMonkey</a>, a platform/website to learn basics of data manipulation: Excel spreadsheets and SQL, the language used for database queries in relational databases like MySQL, PostgreSQL and SQLite. During my studies and personal exploration for the best practices in educational systems, I have encountered many that have been quite horrible (like [TRAKLA2](<a href="http://www.cse.hut.fi/en/research/SVG/TRAKLA2/">http://www.cse.hut.fi/en/research/SVG/TRAKLA2/</a> which is used for teaching algorithms and data structures) and many that have a bit better approach (like <a href="http://www.codecademy.com/">Codecademy</a> and and <a href="http://ville.cs.utu.fi/">ViLLE</a> system for teaching programming).</p>
<p>For me as both as a student and an educator, there are two key factors for making a platform good for educational purposes:</p>
<p>Intuitive User Experience</p>
<p>First one is that the user experience must be so intuitive, easy to follow and pleasant to use that the learner doesn’t have to waste single second thinking about how the system works. Because once you have to focus your attention to things like that, you start losing your motivation and less of your focus goes towards learning the actual subject. The ultimate way to test this, is to see if somebody who understands the subject and can solve problems on say, paper for example, can get through the exercise without problems. From what I have seen, that is also the most difficult part, as UX design often is.</p>
<p>How to help out when learner makes a mistake or doesn’t know what to do</p>
<p>Second, <a href="http://www.huffingtonpost.com/robert-sun/the-role-of-instant-feedb_b_1660459.html">instant and good quality feedback is the bare bone of any education</a>, especially those that don’t involve human interaction with a mentor or teacher. If you keep hitting your head towards the wall because you don’t know what you did wrong, your learning results drop, your motivation drops and you start to generate negative emotions towards the subject. Sometimes it’s just lack of feedback or insufficient feedback but also sometimes the lack of instructions to work with the system.</p>
<p>In programming related platforms, it is quite trivial to separate three things: syntax error, variable error and logical errors. For example, in SQL, syntax error is writing <code>SELCT</code> instead of <code>SELECT</code>, variable error is trying to access a column or table that doesn’t exist, like saying <code>SELECT age, gender, hometown FROM people</code> when the table actually has columns <code>age, gender, city</code>. Especially for SQL, these are quite easy to parse out. And the last one is easy to just test against wanted output.</p>
<h2>Case DataMonkey</h2>
<p>So, how does DataMonkey rank compared to these requirements? Currently for the SQL, there are two separate paths, with the idea of taking first the Guess SQL path and then the Write SQL yourself path.</p>
<p><img src="https://hamatti.org/assets/img/posts/case-study-datamonkey/screenshot1.jpg" alt="" /></p>
<p>For the Guess SQL, there are three exercises that give you no idea on how to construct SQL. There are some blocks that are already in place and others that you need to click to construct the wanted query. But you can click them in any order and just try to pick out anything that sounds like the plain English query. I can see the intention behind that idea but I fail to see how it helps in any way.</p>
<p>First of all, randomly just selecting tokens from the upper row takes the focus out of the actual thing and doesn’t really aid to construct any larger concepts of SQL or how it works. There is no feedback on selecting the wrong one. The idea of starting slow and not providing any information on SQL has its idea but in my idea, trying to teach something without telling anything about it just doesn’t work. And phrasing things like “Using SQL is as simple as choosing between Option A and Option B—it’s just either this or that! So what do you think is so complicated about it?” isn’t really helping.</p>
<p>The next part is what really bothers me. I understand the project is quite new and there are hopefully a big list of things to improve but currently, I couldn’t really suggest it to any of my freshmen students. In the more serious SQL part where you actually write the SQL yourself, the information given is not sufficient.</p>
<p>Referring things done with programming as magic is something that us engineers and developers joke about but when someone is facing things for the first time, saying that something is magic leaves the wrong impression. It is okay to say something along the lines “Okay, at this point, just believe me to write it like this, we will get back to what is happening later.”</p>
<p>The first actual SELECT statement is not described in any way. It says says to type this in and see that oh, we got everything back. As long as you keep repeating everything the instructions say to the point, you are good to go. But once you make a mistake, things go south. As the following screenshot shows, the student gets no information on WHAT went wrong. Learning from mistakes is the most efficient way of learning but when you have to start guessing, we are on the wrong path.</p>
<p><img src="https://hamatti.org/assets/img/posts/case-study-datamonkey/screenshot2.jpg" alt="" /></p>
<p>As I mentioned earlier, syntax, variable and logical errors should be separated with different and more accurate feedback messages. In the above screenshot, there was no column named hometown but it doesn’t even indicate to that direction. Also, sometimes it requires semicolons to terminate commands and sometimes not which can cause really confusing situations.</p>
<p>I really like the idea that DataMonkey is trying to do but the execution is just too way off. Technical cool stuff is irrelevant as long as the important things are off: intuitive user experience and instant feedback. If I would not know anything about SQL and made a mistake somewhere along the line, no matter how subtle, I would be in trouble. I couldn’t even google with the error message since the only one I am getting is platform specific with this platform.</p>
<p>If you happen to be one of the guys working on DataMonkey, please don’t take this too personally, I just got an idea for the blog post when I got the opportunity to look an educational platform with fresh eyes.</p>
Adventures of a Junior Developer
2014-07-19T00:00:00Z
https://hamatti.org/posts/adventures-of-a-junior-developer/
<p>I had never had any real problems in starting a new summer job regardless of the field. I have been building elements for buildings, assembling phones, guiding kids in sport camps, selling home electronics and video recording sport events to name a few. But when it started to be a time to turn my education into a developer job in a real company, I had these weird feelings of fear and anxiety. It seemed that there were many processes and practices that everybody else just knew about and I had no idea.</p>
<p>So I took a freelancer route and kept hacking things for customers either all by myself or with some friends. And in January I joined Chartio as an engineering intern to start my adventure as a junior dev in a real company.</p>
<h2>Getting experience before you get experience</h2>
<p>Getting the first job is usually the hardest part. With no experience, it is difficult to even get interviews. The best solution to help out in that is side projects. Develop something that you have always needed but never found or take an existing thing and try to mimic it. Then put your code on Github, add good readme and you have something to show to potential employers.</p>
<p>Often people are quite shy about their own projects but employers are not looking for perfectly written and structured code from someone with no experience so it’s more important to have something to show, even if it is bit fraqile. To make your Github repository useful, use time to make a good <a href="http://stackoverflow.com/a/2304870/1079129">readme</a> – just reading code doesn’t really tell what your code is supposed to do.</p>
<h2>Interview</h2>
<p>The entire job hunt process is an art. You write dozens of cover letters and applications trying to sell yourself to people who have no idea who you are. But once you get an interview, you may be horrified. Tech job interviews often have a part where you are interviewed about your past and your skills and a technical part where you are given some puzzles or mundane tasks to code in an hour or two.</p>
<p>If you have been in that situation and have completely frozen and failed, do not worry. I have failed too, many times and in absolute basic stuff. In one interview, my task was to parse a simple HTML and printing the results in wanted form – something I do all the time in my own hobby projects. It took me over 20 minutes to even get a good start because I was so nervous and I was barely able to finish that in 60 minutes. Or getting easy freshmen class beginner puzzles and forgetting everything I knew about programming that moment.</p>
<p><img src="https://hamatti.org/assets/img/posts/adventures-of-junior-developer/junior-first-day.gif" alt="" /><br />
source: <a href="http://devopsreactions.tumblr.com/">devopsreactions.tumblr.com</a></p>
<p>But over time, you will get better and you will gain confidence. Just remember to think out loud and don’t be afraid to ask for help if you get really stuck. There are books and websites to help practice for programming interviews, if you feel you need some help in that. Even if you fail to get a correct solution, thinking out loud may help you land your job since the interviewer knows you can think right.</p>
<h2>Code Review</h2>
<p><img src="https://hamatti.org/assets/img/posts/adventures-of-junior-developer/sally-code-review.jpg" alt="" /></p>
<p>For the first few months, code reviews can feel like you are back in school again and performing badly, just waiting for the principal to call you into his office. Because you have probably spent last 15 years or so in school where every review means telling you just how good or bad you were and giving you a grade to remind you of that.</p>
<p>So what are code reviews? Like I said in the beginning, I had no idea about any processes or things to do in a software company so let’s start from the basics. You write code: a bug fix, new feature or some refactoring and you before pushing your code to production, you submit it to code review. We do development in different branches and then use pull requests for review phase but there are also different approaches. Then somebody (usually your fellow developers or a QA team) go through your code and comment on things that need fixing: code style changes, logical mistakes or simple improvements to do the same thing better. Then you fix them, get approved and your code is ready to go into production.</p>
<p>For me, code reviews have been essential in becoming a better developer. After getting over the fear of comments, I have really learned how to take all the improvement suggestions and not only fix that part of the code but to also remember that in the future. And the courage to be able to defend your choices when you know you made a better call than the senior developer commenting. It’s not teacher-student exam-relationship with right and wrong answers, it’s two collegues discussing the best way to code.</p>
The Beautiful Game
2014-07-14T00:00:00Z
https://hamatti.org/posts/the-beautiful-game/
<p><em>I work as an engineering intern at Chartio where we build a data visualization platform for business intelligence use. In addition to developing, I sometimes take interesting datasets and see what I can build with our platform. This blog post was first published in <a href="https://blog.chartio.com/posts/the-beautiful-game">Chartio Blog</a>.</em></p>
<p>Regardless of if you call it football or soccer, fans of the the Beautiful Game were in for a treat for the past month. After a 4-year long wait, FIFA World Cup filled the days of sports lovers all around the world. I love soccer and statistics so I collected some of the most interesting stories of this year’s cup.</p>
<p><img src="https://hamatti.org/assets/img/posts/beautiful-game/overview.png" alt="" /></p>
<p>Soccer is all about the beautiful goals and this year’s 171 goals tied it in most goals with 1998 France World Cup. In the early days, there were less teams and less matches so the total amount was smaller. Lately, the amount of goals per game has settled around 2.5. Last time, we had quite low scoring competition with only 2.2 goals on average. This year we saw couple of games between top teams that resulted in big numbers like Spain and Netherlands group phase game that ended 1-5 and semi-final game between Germany and Brazil where the champions beat hosts 7-1. In the other hand we saw 8 games with no goals in regular time.</p>
<p><img src="https://hamatti.org/assets/img/posts/beautiful-game/goals_per_game.png" alt="" /></p>
<p>More goals were scored in the last 5 minutes of the regular time than any other 5 minute period, so there was no lack of exciting moments or drama. Compared to 2010 World Cup’s two and 2006’s three extra time goals, this year we saw incredible 8 goals, including Mario Götze’s championship winning goal against Argentina in the final. USA’s Clint Dempsey had the honor of scoring the fastest goal in just 29 seconds during their opening game against Ghana. My personal favorite was James Rodriquez’s beautiful volley against Uruguay.</p>
<p><img src="https://hamatti.org/assets/img/posts/beautiful-game/goals_per_minute.png" alt="" /></p>
<p>Some goals win championships but also other are forever written into the history. Germany’s Miroslav Klose became the all-time best scorer with 16 World Cup goals in the devastating 7-1 victory over Brazil in the semifinals. It took Klose 23 games to break Ronaldo’s record. In 1958, France’s Just Fontaine scored 13 goals in only 6 games which still holds the record for most goals in one World Cup.</p>
<p>Miroslav Klose is not the only German who can score. Gerd Müller was the fastest to reach 10 goals, taking him only 5 games in 1970. Of players who are still active, Thomas Müller is the only one who have reached top 30 with 10 goals and is thus Klose’s biggest challenger.</p>
<p><img src="https://hamatti.org/assets/img/posts/beautiful-game/top_scorers.png" alt="" /></p>
<p>Gary Lineker once said that “Football is a simple game. Twenty-two men chase a ball for 90 minutes and at the end, the Germans always win.” This year, Germany won their 4th championship, Brazil and Argentina made it to top 4 but Italy, the third best team in history was eliminated for the second time in a row after the group phase.</p>
<p>Even though soccer is not the number one sport in the US, team USA has started to become a regular face in the competition, making it 6th time in a row to get through qualifications. Hopefully soccer will gain more and more interest here so maybe in 2018 or 2022 USA will make it to the quarter finals.</p>
<p><img src="https://hamatti.org/assets/img/posts/beautiful-game/placements.png" alt="" /></p>
<p>After an intensive month with the World Cup, I have an empty feeling. This World Cup was a great tournament with some memorable games and now there are another 4 years with no World Cup. I hope we will see Finland in the World Cup in Russia 2018 or in Qatar 2022. Until then, I will keep cheering for the South American teams.</p>
Becoming a better programming teacher
2014-06-01T00:00:00Z
https://hamatti.org/posts/becoming-a-better-programming-teacher/
<p>About a week ago I got an email through San Francisco Ruby Meetup Group about a workshop on becoming a better programming teacher and given my background and passion for education, it was a no-brainer to jump in. A four hour workshop was organized by <a href="https://generalassemb.ly/education/becoming-a-better-programming-teacher">General Assembly</a>, a company that provides bootcamp like programs, part time courses, classes and workshops for everyone who wants to become a better developer or change their career into development.</p>
<p>I was especially interested because of the pair programming aspect of the workshop. In our university in Turku, Finland we use pair programming in freshmen programming classes (with 100-150 students) as a key part of our course logistics and it has proven to be really powerful tool to enhance learning experience and to get students to meet new people. Every week for 2 hours, we have a session in a lecture hall where students pair program through a set of exercises in a <a href="http://ville.cs.utu.fi/">platform</a> that provides instant feedback and records students’ progress so both students get the points they earn while doing it together on one computer. We don’t really enforce any pair programming paradigms but students change the one who’s driving every 15 minutes or after each exercise so both students get to drive and navigate.</p>
<p><img src="https://hamatti.org/assets/img/posts/becoming-better-programming-teacher/pair-programming.jpg" alt="" /><br />
Source: <a href="https://www.flickr.com/photos/78453620@N00/552208117/">https://www.flickr.com/photos/78453620@N00/552208117/</a> (CC BY 2.0)</p>
<p>My job is to bring you to the edge of tears without making you cry</p>
<p>In a workshop of about 20 people, it was great to hear experiences and opinions about pair programming in different situations: small classrooms, bringing new junior developers up to speed or even in interview situations. I was expecting to catch a few new tricks to my teaching repertoire but in addition to that, I got an experience that opened my eyes. Not only did we discuss about how people feel about using pair programming but we also did a little experience of how it would feel like to be a student with little to none idea of what he is doing.</p>
<p><img src="https://hamatti.org/assets/img/posts/becoming-better-programming-teacher/game-of-life.gif" alt="" /><br />
Source: <a href="http://en.wikipedia.org/wiki/File:Gospers_glider_gun.gif">http://en.wikipedia.org/wiki/File:Gospers_glider_gun.gif</a> (CC BY-SA 3.0)</p>
<p>We had a task of building <a href="http://en.wikipedia.org/wiki/Conway's_Game_of_Life">Conway’s Game of Life</a> but the catch was to do it in Fortran since that would be a new experience for (almost) all of us. If you are unfamiliar with Game of Life, read more from the link but it is a cellular automaton system that is often used in code retreats. And that was the moment that reminded me what our students go through. To my surprise, I found myself kinda petrified. Suddenly I was really reluctant to do this, thinking that the other developer would think I’m a bad developer, I felt like I don’t want to do this because I really suck in Game of Life (I have done it in multiple code retreats and still I have no idea how to do it). And in Fortran? I have no idea how to code Fortran or even what kind of language it is.</p>
<p>And so I realized that those are the exact feelings our students go through and I had not been thinking about that at all previously. And let me say, I am a decent programmer and still I felt like that. Just imagine people who have programmed for about a week, don’t know even what questions to ask and we put them to pair program with people they have just met or have never met before. I am not saying we should not do it. We absolutely should, it is really efficient way of learning and it vastly improves learning results of our students. I am just saying that only now I really understand what some of our students are going through in our classes.</p>
<p>After I have finished my internship in San Francisco and I go back to university, I definitely have a better understanding and some new tricks up my sleeve to make the pair programming a better experience for our students.</p>
Rails Girls San Francisco
2014-01-20T00:00:00Z
https://hamatti.org/posts/rails-girls-san-francisco-2014/
<p><a href="https://hamatti.org/posts/rails-girls-building-the-future/">After coaching at Rails Girls Helsinki last November</a> and getting a job in San Francisco, one of the first things I did was checking out if there would be a Rails Girls event here too. And how lucky was I. Last weekend, almost 100 enthusiastic girls and women took their laptops and came to <a href="https://www.engineyard.com/">Engine Yard</a> for two-day workshop to learn about web development with Ruby on Rails. Since I already wrote about Rails Girls after the last event, I won’t dig too deep into what it is.</p>
<p>In Helsinki, most of the participants were people who had no previous experience on programming but here in San Francisco it was a bit different story. Many girls had done some coding or even worked as web developers but wanted to learn Ruby on Rails to go deeper to the rabbit hole. From a coach perspective, it makes a difference. When teaching basically same things to people with no experience at all and to people who work as developers, you really need to take a different approach. Coaching both different groups was equally fun but in different ways. In the event, I shared a group of three students with a fellow coach and the event having much smaller student per coach ratio gave us an opportunity to give even better personal coaching throughout the day.</p>
<p>Before the event, I was so nervous. I wasn’t completely sure if my skill set would be at San Francisco level but quite quickly I found out that it’s really the same thing everywhere. On Friday between work and installation party I learned how to install Ruby on Rails to OSX Mavericks, now that I have one myself. The installation party was a huge success. It was so great to meet all new people, hear their backgrounds and motivations for coming to Rails Girls and talking about things beyond just the workshop.</p>
<p>For Saturday, we had slightly different program than we did in Helsinki and that is one thing I love about Rails Girls. It is not too strict with all those rules that what the event has too look like. It’s all open source and every event looks like their organizers and participants. We started with quick intro to <a href="http://tryruby.org/">tryruby.org</a> and then went to coaching groups to go through the basic tutorial. Around lunch there were couple of lightning talks about Git and databases as well as the bento box exercise. After that we continued with extra tutorials about deploying to Heroku, adding user control, commenting system and all the cool stuff.</p>
<p>And of course, because Rails Girls is not only about coding but about networking and meeting new people, the after party on the rooftops was so awesome. It was nice to reflect with everyone how they felt about the day and to talk with people who had come to San Francisco from all around the country and the world. As it’s only been 18 days since I moved here, it’s always fun to hear other’s experiences about moving to Bay Area.</p>
<p>If you have always wanted to learn more about tech, web development and coding but have never quite figured out how, I can honestly recommend Rails Girls. Check out the next local events on <a href="https://railsgirls.com/">Rails Girls website</a> and participate!</p>
Command line magic with git and bash history
2014-01-16T00:00:00Z
https://hamatti.org/posts/command-line-magic-with-git-and-bash-history/
<p>So, in the beginning of January I started as an engineering intern in Chartio and since that my workflow has really improved a lot and I’ve learned and discovered some tricks.</p>
<p>First one I want to share, made a huge difference for me:</p>
<p>So, whenever I had previously needed to run a bash command again, I would do <code>history | grep [keyword]</code> and then look at the number of the command I want to rerun and run <code>![number]</code> to rerun the command. And I was quite happy, it was efficient enough for me not to try and google for some better way.</p>
<p>Until last week, when my coworker showed me that using <code>CTLR+r</code> on bash prompt (using emacs mode), bash enters a reverse-search mode where you can type a part of the command and bash goes through the history and suggests starting from latest command a matching one. Pressing <code>CTLR+r</code> again, it moves one step further in history. You can run the command or modify it on the fly. So instead of two commands, you can just hit <code>CTLR+r</code> and <code>ENTER</code> to run the command. Awesome.</p>
<p>Another thing I discovered has to do with git. If you don’t yet know or use git, start now. There’s no reason for you to not use it. Anyway, my git usage has gone through the roof now that I started working here. So, when using branch names like <code>bugfix-1234</code>, <code>issue-1950</code>or so, it does get bit hard to remember what those are all about. And I’m very visual guy so I have post-its all around my desktop and display to keep me on track of things.</p>
<p>So I wrote some bash functions and git aliases to make things more convenient for me. In my ~/.bash_profile, I have following:</p>
<pre class="line-numbers language-bash"><code class="language-bash">
function parse_git_branch_name {
git rev-parse --abbrev-ref HEAD
}
function parse_git_description {
git config branch.$(parse_git_branch_name).description
}
alias gs='echo $(parse_git_branch_name): $(parse_git_description) && git status'
</code></pre>
<p>First function gets the name of current branch and second on gets description saved to the branch defined by <code>git branch --edit-description</code>. For easier editing, I also have a git alias in ~/.gitconfig for editing description:</p>
<pre class="line-numbers language-bash"><code class="language-bash">
[alias]
edit = branch --edit-description
</code></pre>
<p>So everytime I want to add or edit a branch description, I do <code>git edit</code> and my habit is to do gs all the time so then I see the description followed by status of my git repo. Awesome.</p>
Difficulties with teaching and learning programming
2013-11-25T00:00:00Z
https://hamatti.org/posts/difficulties-with-teaching-and-learning-programming/
<p>It’s no secret that learning programming is – at least for some of us – an obstacle course. Personally, it took me years to grasp the skills to be a some-what good programmer. For the last 3 or 4 years I’ve been involved in teaching programming basics for freshmen in my university. I started with helping fellow students, then started as a mentor for department and nowadays in addition to previous, I work as a part-time teacher on our courses.</p>
<p>I’ve come to conclusion that one of the reasons for it being so difficult is, that one needs to learn a huge amount of different things at the same time for being able to move forward.</p>
<p>I think that there are three important subtopics that students need to learn at the same time: programming language syntax, what can be done with programming languages and problem-solving. For many students, it seems that they don’t really realize that there is more in the classes than just learning the syntax: most of our exercises and problems don’t arise from that need but from learning problem-solving. Too often do I hear the sentence “This has not been taught to us” while assisting students with their exercises. True, they have not been told the solution but they have been given all the tools to code but they need to come up with the solution themselves.</p>
<p>One of the hardest parts in teaching is to remain on the level of solving everything with basic structures: we often tend to know trick or two to easily solve the problem on a one-liner which once again raise the statement “but they haven’t taught us this”. I’ll take an example from our course and how it can be solved quite differently.</p>
<p>We use Java as teaching language, so bear with me.</p>
<p>The exercise is: “Given a string that contains a binary presentation of a number, calculate and print the 10-base value.”</p>
<pre class="language-java"><code class="language-java">public static void binaryToInteger(String binarystring) {
int tenBase = Integer.parseInt(binarystring, 2);
System.out.println(tenBase);
}</code></pre>
<p>This is of course a valid, quick and smart way of doing it. But I think we all can agree that it doesn’t really teach you anything – maybe besides finding the solution on Google. Well, let’s use a for loop:</p>
<pre class="language-java"><code class="language-java">public static void binaryToInteger(String binarystring) {
int sum = 0;
int exponent = 0;
for(int i = binarystring.length() -1; i >= 0; i--) {
if(binarystring.charAt(i) == '1') {
sum += Math.pow(2, exponent);
}
exponent++;
}
System.out.println(sum);
}</code></pre>
<p>Here, it seems like now it’s more like a rookie version. And this is what is often showed to students. “But we haven’t been taught to use Math library” and yes, they have not. At this point, we could argue if teaching reading the API and googling for what it has to offer is what we should teach at this point. But often it might be too early and here we are not just trying to solve a problem but also to practice basic structures. So usually at this point student finally gets bit sad to see that oh, even the more experienced coders can’t solve this. That’s because we often don’t need to go down to the lowest level of implementing the pow()-function.</p>
<p>So:</p>
<pre class="language-java"><code class="language-java">public static void binaryToInt(String binarystring) {
int sum = 0;
int exponent = 0;
for(int i = binarystring.length() -1; i >= 0; i--) {
if(binarystring.charAt(i) == '1') {
int power = 1;
for(int j = 0; j < exponent; j++) {
power *= 2;
}
sum += power;
}
exponent++;
}
System.out.println(sum);
}</code></pre>
<p>After implementing the power function ourself, we have reached a solution which is done with basic structures that have been taught. That is the difficulty of assisting and helping students: how to keep everything down to the very lowest level that they have been taught.</p>
<p>Another issue is what student really has to know and learn to be able to deal with situations like this. Remember that the students I’m talking about, are people who have very little programming experience. Basically 3-4 weeks of our Java course.</p>
<p>So students have to have a strong feel on basics. Ideally, they would have been tinkering around with programming for those weeks. Usually, the truth is closer to students having only done compulsory exercises. Luckily we have quite a lot of those exercises so they have done more than we used to couple of years ago. Next comes the tricky part. Student needs to start breaking the problem into smaller, computer-understandable pieces. For most, this is the most important and most difficult part.</p>
<p>Ability to break problems into smaller, computer-understandable pieces is something that obviously comes only with experience: once you know what is possible, you know how the problem needs to be formulated. And it is something, that cannot be directly taught. If we tell people direct approaches to solving problems, the exercises become insignificant. We can only point the way.</p>
<p>In the end of the day, it’s all about attitude towards learning that counts. If you have an open-minded approach and willingness to learn, you will learn to program, no questions asked. But for most of us, it won’t be easy and it will take time. But in the end, it’s worth it.</p>
<p>(For my students who reached the end, please do not be offended. I still love you and I still continue to help you every way possible.)</p>
Computer illiterate generations – what should we do?
2013-10-17T00:00:00Z
https://hamatti.org/posts/computer-illiterate-generations-what-should-we-do/
<p>There has been a lot of discussions during the last years about the IT education of young people. Today, the topic once again popped out in Finnish news when Finnish Broadcasting Company wrote <a href="http://translate.google.com/translate?sl=fi&tl=en&js=n&prev=_t&hl=en&ie=UTF-8&u=http%3A%2F%2Fyle.fi%2Fuutiset%2Fsuomen_koulujen_it-opetus_jordanian_tasolla_-_uutta_mallia_haetaan_virosta%2F6885099&act=url">a news piece</a> (Google Translate to English, quite decent) about the level of IT education in Finnish elementary schools. One of the maybe most popular blog posts about the situation globally is Coding2Learn blog’s <a href="http://coding2learn.org/blog/2013/07/29/kids-cant-use-computers/">Kids Can’t Use Computers… And This Is Why It Should Worry You</a> which is simply brilliant.</p>
<p>In Estonia, a long vision program <a href="http://en.wikipedia.org/wiki/Tiigrih%C3%BCpe">Tiigrihüpe</a> started already in 1990s with aim to provide better infrastructure and education regarding technology. Year ago they started a new program within Tiigrihüpe called <a href="http://www.tiigrihype.ee/en/programming-schools-and-hobby-clubs">ProgeTiger</a>.</p>
<blockquote>
<p>The Tiger Leap Foundation has launched a programme called ProgeTiger which teaches programming, web applications and website creation during classes or in hobby clubs to students from grades 1 to 12. –ProgeTiger</p>
</blockquote>
<p>They are going to provide all kids in elementary school with some understanding of programming – not to create software engineers or hackers but to give kids better understanding of the world that is more and more run by computers.</p>
<p>I do support this kind of progress. I think it’s vital to teach basic programming skills and computer understanding in schools to new generations. Couple of points why:</p>
<p>You use dozens of devices daily. All those devices are run by computer and if you have no idea how they work, you have less understanding about what they can be used for (both for good and evil).<br />
Understanding how Internet and applications within work makes you more capable of doing things there. It also gives you better awareness to hoaxes, phishing and basic security.<br />
Programming develops logical reasoning and problem solving – skills that are useful in all fields of life, not only IT.<br />
It’s also fun and creative but I think that’s not a real good argument for making something mandatory.</p>
<p><img src="https://hamatti.org/assets/img/posts/computer-illiterate/google.jpg" alt="" /></p>
<p>Often these discussions these days rally around programming. And it’s a big leap to take in the education system so I also want to take another kind of approach to make things better:</p>
<p>I believe most (almost all) schools that have computers for students and teachers to use, have very strict user right restrictions in them. When I was back in school, it was very limited what you could do with the computers. You could open a browser, “create” something in Microsoft Office tools and… well, that was pretty much it. Often even the teachers didn’t have any rights to install software, alter settings or do elementary computer stuff.</p>
<blockquote>
<p>Our network infrastructures in UK schools is equally to blame. We’ve mirrored corporate networks, preventing kids and teachers access to system settings, the command line and requiring admin rights to do almost anything. They’re sitting at a general purpose computer without the ability to do any general purpose computing. They have access to a few applications and that’s all. – Coding2Learn blog</p>
</blockquote>
<p>What is this supposed to teach our kids about using computers?</p>
<p>How about we remove those restrictions? What if we just give students and teachers full rights on the computers, let them install software and experiment with different OS settings. Let’s give them possibility to try things out, install different operating systems (which I also think should be taught in school for everyone) and see what happens. As an admin you can always create disk images to run fresh installations nightly to make sure the computers are usable on the next day.</p>
<p>The more I’ve been involved in teaching and education, I’ve realized that elementary school should be a place where kids could try things without a fear of failure. When you break something, you learn that you did something the machine wasn’t prepared for. You learn the limits and it broadens the area where you are comfortable in.</p>
<p>Linda Liukas, the founder of Rails Girls, said it perfectly in her speech in Railsberry 2012:</p>
<blockquote>
<p>“I love Tumblr and Facebook but they are doing something profoundly evil for my generation. They are putting us in to a mode where we only like and reblog stuff – so we consume and curate but never actually create.” – Linda Liukas</p>
</blockquote>
Rails Girls – building the future
2013-09-09T00:00:00Z
https://hamatti.org/posts/rails-girls-building-the-future/
<p><em>Chicago, Taipei, Groningen, Helsinki, Zilina, Oulu and Dresden. 7 Rails Girls events this weekend all around the world.</em></p>
<p>Today was my first ever <a href="https://railsgirls.com/">Rails Girls</a> event in Helsinki. I ain’t much of a Rails or even a Ruby, I tend to choose Python as my weapon of choice. However, I’ve been long interested in Rails, done some hobby projects with it and even more than that, I’ve been into teaching new people to programming for couple of years now at the university. So coaching a Rails Girls event was super exciting new experience.</p>
<p>I have also been a huge fan of whole movement since its very beginning and have always had some interest into participating but never really had the courage to actually go there and coach with people who I don’t know previously in a language I’m not expert at. But I’m glad I finally did send out the mail that I’m interested and joined the team.2013-11-09 14.12.52</p>
<p>For those who are not familiar with Rails Girls, it’s a two-day event where girls and women of all ages and backgrounds who have never programmed before, come to an event where bunch of volunteer coaches help them through a tutorial of building their very first web app in Ruby on Rails. Not only do they become somewhat familiar with Ruby on Rails and web development, they also meet a big group of other people who share the same enthusiasm and who are at same level – basically people who have no experience on programming but want eagerly to learn more.</p>
<p>Having thought programming for freshmen and students of other disciplinary for 18 months at the university now, Rails Girls was a wonderful opportunity to have a little breakout from academic world and to see new, more unformal ways of teaching to inspire my regular teaching methods. I had a team of five girls and after a slow start, they picked up quite quickly and started to ask really great questions – and especially questions that differ from what I get asked at the Uni. At the end of the day, 3 out of 5 got their first app deployed in Heroku (something I had never done before but know now how to do!) which was something that really made them happy.</p>
<p><img src="https://hamatti.org/assets/img/posts/rails-girls-building-the-future/group.jpg" alt="" /></p>
<p>Personally, I learned a lot about Ruby on Rails, got new experience with teaching beginners and made a bunch of new friends. And I loved every moment. Many of the girls told me that they had been bit suspicious beforehand but after participating felt like it was an awesome event and wondered why they hadn’t done it before. So if there is any female eager to learn to code but maybe bit worried about not being good enough, come to Rails Girls event (<a href="http://railsgirls.com/events">upcoming events can be found here</a>), make some friends and start learning something new.</p>
69 Lines of SQL
2013-06-30T00:00:00Z
https://hamatti.org/posts/69-lines-of-sql/
<p>Sometimes I wonder what programming really is and why we hackers find it so compelling. Most of the time we – or atleast I – bang our heads to wall for hours while trying to conquer the challenge. After all those hearth-breaking moments we finally succeed to get the code running and it solves the problem. All the dark clouds disappear, flowers start to bloom and happy squirrels are jumping everywhere. I had one of these moments today while trying to do some SQL magic.</p>
<p>I do SQL and database operations for almost every project I do. I was relatively good at database courses at the university and I always thought that SQL is so simple. Yeah, about that… I only evere had to do very basic queries which just involved some basic SELECTs with couple of tables usually just JOINin them on their keys. Today, however, I was wondering how I could make a sports statistics database – web page connection better and I started to think if things could be done differently than I usually do while dealing with this kind of data.</p>
<h2>The Data:</h2>
<p>The sports data is as follows: there are individual games (id, home team, away team, home goals, away goals and tournament) which belong to tournaments which belong to seasons. Previously I always had to tables for the game data – the indivual games and a stats table to which I calculated the games, wins, draws, losses, goals for, goals against and points every time I added or removed a tournament from the db.</p>
<h2>The Need:</h2>
<p>Sometimes it might be a good idea but I wanted to keep the data in just one place so I put my SQL-magician hat on and started to think if I could do all that magic with pure SQL. It would help me to easily generate different statistics. I could just choose a particular season or tournament or sets of tournaments to combine. And oh boy, it was not an super-easy task.</p>
<h2>The Code:</h2>
<p>My code ended up being 69 lines of SQL that does just the trick. (<em>edit 2019-02-09, the code does not exist anymore</em>) I collect all the different things I need with separate subqueries and then join them together in one. Looking at it now, after finishing, it seems like obvious thing. But it took me 4 hours to tackle the situation and make all small pieces fall together. I’m very proud of the code, even if it’s not the best or optimal solution, it gives me huge opportunity to now get the data to my web site in a way I want it for each situation.</p>
TDD Pair Programming at University
2013-04-08T00:00:00Z
https://hamatti.org/posts/tdd-pair-programming-at-university/
<p>Today was surprising day at the university. I’m taking a class called Designing Object-Oriented Software which has before been somewhat boring 7 x 90 min lectures and an exam but this year our professor hired a guy called Aki Salmi to organise 4 workshops á 4 hours. Beforehand I knew Aki is a guru and an excellent guy but I was still surprised to see how much fun studying at best could be.</p>
<p>The idea in the first day was to start implementing <a href="http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway’s Game of Life</a> in Test-Driven Development (TDD) style with pair programming. For me, the TDD was a new approach – I’ve done it very little in <a href="https://class.coursera.org/programming2-001/class/index">Learn to Program: Crafting Quality Code</a> and also in <a href="https://class.coursera.org/progfun-002/class">Functional Programming Principles in Scala</a> but it has been merely a sneak peek. Pair programming on the other hand was something I had never done before. This time, we were thrown to the wolves with just a few bits of information:</p>
<p>Rules of Game of Life, very small stub on how to make tests with JUnit, Mockito and Hamcrest and order to create tests first and start implementing after that. At first it was of course really challenging to adopt a new way of thinking (TDD) and also a new way of doing (pair programming) especially when Aki really challenged us to think on our own and responded most implementation-related questions with something like “try it out”. Towards the end we found out that we were drifting away from TDD concept but we managed to get back on the saddle before the day ended.</p>
<p><img src="https://hamatti.org/assets/img/posts/tdd-pair-programming/cell-test.jpg" alt="" /></p>
<p>First workshop of 4 hours is now behind and I programmed with a guy I’ve never written code with or even discussed about programming with but who I knew from student organisations. We were encouraged to work as pair programming ping-pong where one writes the test and another makes it pass and then we refactor together and for the next iteration we changed roles. It turned out to be a great way to stimulate new thinking and I found out that pair programming can be really fun. I had seen few presentations about pair programming and always thought it was stupid but now really trying it out it turned out to be something that really fits in to my workflow.</p>
<p>With three more workshops to come, I will definitely take the TDD as bigger part of my workflow on personal projects and I’m eager to learn more.</p>