Friday Facts #366 - The only way to go fast, is to go well!

Regular reports on Factorio development.
Luxalpa
Manual Inserter
Manual Inserter
Posts: 1
Joined: Fri Jun 18, 2021 9:52 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Luxalpa »

I must say I'm honestly surprised that the dev team does not use Functional Reactive Programming for their UI code (and things like the building code), as these complex state interactions / asynchronous actions are exactly the kind of use case for this paradigm. I'm pretty sure using that for your GUI code would make working with it a general piece of cake and at least for UI code, it is provable that Reactive Programming is the most performant possible approach as well.

User avatar
irbork
Fast Inserter
Fast Inserter
Posts: 229
Joined: Fri Jul 04, 2014 1:17 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by irbork »

I did not particularly enjoyed that one, but at least I know that you guys are still alive (despite COVID-19), and working on my favorite game.

User avatar
bormand
Fast Inserter
Fast Inserter
Posts: 201
Joined: Fri Jun 05, 2020 9:59 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by bormand »

Luxalpa wrote:
Fri Jun 18, 2021 9:57 pm
it is provable that Reactive Programming is the most performant possible approach
With a general-purpose compiler? I doubt.

With a specialized compiler that was designed to fold those reactive patterns into usual loops? Probably...

Could you please give some links that support this claim?

User avatar
BlueTemplar
Smart Inserter
Smart Inserter
Posts: 2206
Joined: Fri Jun 08, 2018 2:16 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by BlueTemplar »

Luxalpa wrote:
Fri Jun 18, 2021 9:57 pm
[...]
Functional Reactive Programming for their UI code (and things like the building code), as these complex state interactions / asynchronous actions are exactly the kind of use case for this paradigm.
[...]
So, what is FRP ?

User avatar
NotRexButCaesar
Smart Inserter
Smart Inserter
Posts: 1058
Joined: Sun Feb 16, 2020 12:47 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by NotRexButCaesar »

BlueTemplar wrote:
Fri Jun 18, 2021 10:30 pm
Luxalpa wrote:
Fri Jun 18, 2021 9:57 pm
[...]
Functional Reactive Programming for their UI code (and things like the building code), as these complex state interactions / asynchronous actions are exactly the kind of use case for this paradigm.
[...]
So, what is FRP ?
Here is the wikipedia article: https://en.wikipedia.org/wiki/Functiona ... rogramming.
: Alea jacta est. Determine what you intend to accomplish with an action before execution.
Have you ever heard the gospel? Most have not.

seltha
Burner Inserter
Burner Inserter
Posts: 15
Joined: Tue Jul 23, 2019 8:38 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by seltha »

Great read! Love the bit about the shareholders not wanting us to waste time refactoring... that's exactly what's wrong at work, we're always rushing on to the next feature, never paying down the tech debt and it's dissatisfying seeing the same error popping up again and again which we have to manually fix in the database again and again.

I have a (perhaps naive) question about GUI... it seems like every game ever made invents a GUI system from scratch that all do the same things... text box, button, check box, image/video/interactive tutorial pane, toolbar, hit points indicator, etc. To me this feels like millions of developer hours wasted because "that other person's wheel isn't quite round enough". What's the thought process behind building a GUI vs bringing in (and contributing to) an open source library?

PS: Uncle Bob is amazing! I've listened to a few podcasts of his, thanks for the youtube link - that will be good watching!

User avatar
bormand
Fast Inserter
Fast Inserter
Posts: 201
Joined: Fri Jun 05, 2020 9:59 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by bormand »

seltha wrote:
Sat Jun 19, 2021 12:52 am
Uncle Bob is amazing! I've listened to a few podcasts of his, thanks for the youtube link - that will be good watching!
Yeah... But I take his "ultra-factoring" concept with a grain of salt. Why?

To begin with, common languages like C++ or Java have a lot of a visual noise. Sure, those three-line functions may look "like a prose" in something like Haskell, but in C++ they just stomp SNR to the floor, making code harder to read. I see a lot of curly braces, parentheses, argument types, spaces, commas... where is my code?

Secondly, it's extremely hard to factor complex functions right. And if you've read Robert's book, you know what I'm talking about. I had a lot more WTF moments while reading his examples after refactoring. Bunch of three-line functions that toggle random mutable state here and there. WTF?! And if the algorithm is torn apart by three-line pieces, it's very hard to understand what invariants should hold for that mutable state.

Third thing is performance. Abstractions are cool, but they aren't free. Even in release mode they easily confuse the compiler. And in debug mode you pay the full price, so you can drink a tea if you want to debug anything larger than unit test. I agree, we shouldn't debug clean code often. But what to do when I should? I use a lot of third party libraries, after all.

So, I don't believe into the "extract methods until you can't do it anymore" mantra. Finding a good balance is still an art.

seltha
Burner Inserter
Burner Inserter
Posts: 15
Joined: Tue Jul 23, 2019 8:38 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by seltha »

bormand wrote:
Sat Jun 19, 2021 4:52 am
Finding a good balance is still an art.
I think that is true in most walks of life, but especially in software. 3000 lines of code in one file, vs 1000 files with 3 lines in each... you need to find some happy medium. Developers often get swept up by an ideal and try applying it to every situation, even if it doesn't make sense to apply it. Ever tried using a NOSQL database for relational data because 'oh, you have to use NOSQL, that's the way we do things round here'. Always try to get the right tool for the job. :)

User avatar
freeafrica
Inserter
Inserter
Posts: 40
Joined: Mon Aug 19, 2019 2:33 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by freeafrica »

kovarex wrote:For this, I implemented a simple test dependency system. The tests are executed and listed in a way, that when you get to debug and check a test, you know that all of its dependencies are already working correctly. I tried to search if others use the dependencies as well, and how they do it, and I surprisingly didn't find anything.
It's an interesting concept, I think I'll make use of that, however one idea/advice pops to my mind.

In a lot of cases, randomizing test execution order can raise light on bugs. In fix-ordered execution a previous test could've caused a side-effect which may have made an otherwise rightly failing test case pass, thus hiding a (testing/production) bug. Also, having to ensure randomized test execution success raises the robustness of your test-suite.

My advice would be, to have this dependency-based execution order for development purposes, but also have an execution environment with randomized order. That should also output it's test-execution order-chain, so you could reproduce the raised issue.

User avatar
BlueTemplar
Smart Inserter
Smart Inserter
Posts: 2206
Joined: Fri Jun 08, 2018 2:16 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by BlueTemplar »

NotRexButCaesar wrote:
Fri Jun 18, 2021 10:32 pm
BlueTemplar wrote:
Fri Jun 18, 2021 10:30 pm
Luxalpa wrote:
Fri Jun 18, 2021 9:57 pm
[...]
Functional Reactive Programming for their UI code (and things like the building code), as these complex state interactions / asynchronous actions are exactly the kind of use case for this paradigm.
[...]
So, what is FRP ?
Here is the wikipedia article: https://en.wikipedia.org/wiki/Functiona ... rogramming.
But they *already* seem to be using asynchronous signals and events, so why does Luxalpa thinks that they aren't using FRP ? ("Too many" classes, "not enough" functions ?)
And is C++ even the right language to do FRP in ?

User avatar
BlueTemplar
Smart Inserter
Smart Inserter
Posts: 2206
Joined: Fri Jun 08, 2018 2:16 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by BlueTemplar »

seltha wrote:
Sat Jun 19, 2021 12:52 am
Great read! Love the bit about the shareholders not wanting us to waste time refactoring... that's exactly what's wrong at work, we're always rushing on to the next feature, never paying down the tech debt and it's dissatisfying seeing the same error popping up again and again which we have to manually fix in the database again and again.

I have a (perhaps naive) question about GUI... it seems like every game ever made invents a GUI system from scratch that all do the same things... text box, button, check box, image/video/interactive tutorial pane, toolbar, hit points indicator, etc. To me this feels like millions of developer hours wasted because "that other person's wheel isn't quite round enough". What's the thought process behind building a GUI vs bringing in (and contributing to) an open source library?

PS: Uncle Bob is amazing! I've listened to a few podcasts of his, thanks for the youtube link - that will be good watching!
AFAIK before Wube made their own graphic engine, Factorio used an open source library : Allegro, but being pretty old, it ended up by limiting Factorio ?
https://www.factorio.com/blog/post/fff-230

P.S.: Also, not "every game ever made", isn't this one of the things that Unity helps with (with a big performance tradeoff) ?

Serenity
Filter Inserter
Filter Inserter
Posts: 969
Joined: Fri Apr 15, 2016 6:16 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Serenity »

This really confused me until I remembered operator precedence:

Code: Select all

&this->resetPresetButton
At first glance this looks like you're taking the address of the this pointer :)

User avatar
ptx0
Smart Inserter
Smart Inserter
Posts: 1120
Joined: Wed Jan 01, 2020 7:16 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by ptx0 »

Muche wrote:
Fri Jun 18, 2021 3:03 pm
eradicator wrote:
Fri Jun 18, 2021 2:16 pm
The dependencies aren't explicitly written down anywhere, they're just implicit in the order that tests are run.
I've read that an advantage of randomized test order is it could reveal hidden inter-test dependencies (e.g. a later test doesn't set the test environment properly and "relies" on an earlier test to do it).
most good unit test suites set up a new environment for each test.
My Mods - Fish Per Minute base size metric - Use your crashed spaceship as a belt balancer?
• • •
Base: Bob's @ 1 Million SPM
• • •
Linear search and overflows are indicative of sloppy coding practices.

yagaodirac
Fast Inserter
Fast Inserter
Posts: 151
Joined: Sun Jun 16, 2019 4:04 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by yagaodirac »

You should separate the LuaEntity.html by the types.

Peregrin_Tooc
Burner Inserter
Burner Inserter
Posts: 7
Joined: Fri Sep 29, 2017 2:49 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Peregrin_Tooc »

Ah, another new disciple. Excellent :mrburns:

I suggest you checkout the books on refactoring by Martin Fowler (my key takeaway: if your tests fail unexpectedly when you refactor, your steps are too big) and on TDD by Kent Beck (who demonstrates how to write a unit test framework in a TDD way and plenty more. It's a great read) and that you check out the practice of acceptance test driven development ATDD) where you write end-to-end tests first and then break them down into more and more specific tests (e.g. by cutting out the GUI, cutting out all event handlers... basically you implement each component on its own, having the e2e-tests to make sure that once you're done, the feature works).

If I weren't so comfortable in my current job, I'd definitively consider applying for a job at Wube :)

Feaelin
Manual Inserter
Manual Inserter
Posts: 4
Joined: Sat Jun 19, 2021 1:44 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Feaelin »

"If the tests don't have any special structure, the situation when 100 tests all fail at the same time is very unfortunate, all you are left with is to try to pick some test semi-randomly, and start debugging it."

Assuming one is not doing a code redesign, having a large number of tests fail at once when making changes signals some possible design flaws in the production code. It can signal that code that has the same reason(s) to change is either "all over" or in a large class that is doing too much. Either one can result in a large number of tests failing. If that's happening when I make a small change (and I try to keep each change as small as possible), I start looking for how I can move code around so that code that has the same reason to change is decomposed in a cleaner way.

Feaelin
Manual Inserter
Manual Inserter
Posts: 4
Joined: Sat Jun 19, 2021 1:44 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Feaelin »

"When you do small changes the yes/no indication of tests is enough, but it isn't always an option, especially when you refactor some internal structure, in which case you kind of expect to break a lot of stuff, and you need to have a way to fix them step by step once it can compile again."

*nods* This can happen during redesign, if the redesign isn't broken down into small enough refactoring steps. Someone else has already recommended Martin Fowler's Refactoring book, I second that recommendation: https://martinfowler.com/books/refactoring.html

One reason to isolate dependencies:
It is desired that unit tests be _fast_ and run frequently. My idealized pattern for TDD is:
  1. Run all the tests for the project to ensure I'm starting in a good place.
  2. Write a small specific new test for a class that requires the change.
  3. Run all the tests for the class being modified, implement a small change that passes the tests.
  4. Repeat until the series of tiny changes hits my threshold for a commit.
  5. Run ALL tests in the project to ensure that I haven't broken something somewhere else (which I won't if I succeed with my refactoring skills learned from Fowler, above).
  6. Commit the changes.
  7. Repeat this pattern until I've implemented the feature.
The thing to note here is that I'm running the tests for the classes I'm working with almost constantly. I'm running the entire suite of tests pretty often as well. Therefore they need to be fast or they'll (feel like) they're slowing me down. So one of the motivating factors to isolating a test dependency is to make it so _most_ of the tests are fast. To do that, we abstract away a class or subsystem that is slow. A good example is a section of code that accesses the file-system or database. Then we isolate other units from that system in our tests so that most of tests remain fast.

Ideally, when a subsystem has been isolated in the other tests, there's at least a small number of integration tests that insure that the other subsystems interact with the slow subsystem correctly. Typically, such tests are grouped together and run less frequently -- perhaps before putting a new feature or bug fix into a test environment.

There are other reasons as well for isolating certain kinds of systems from most of our tests. Generally, it can lead to better decoupling between classes, more clearly intentioned classes, etc.

"Unit" is a deliberately vague term. It is super common for OO programmers to treat it as synonymous with "class" or for non-OO programmers to treat as synonymous with "file", etc. That is, there's a tendency to target classes/files with unit tests. However, that isn't necessarily the best approach. If a series of classes comprise a subsystem, those classes will be tightly coupled with each other. It can be merited for the "unit under test" be that subsystem.

A way to think about the unit tests is that they should test through "public interface" of a given subsystem. In this context, I'm using "public" to mean "public to the rest of the codebase". I've found the ideal is to test through the methods that the rest of the code will be using to access the unit-under-test's functionality.

This is also a valuable way to think of refactoring -- "how do I change the behavior of this unit WITHOUT changing its public interface?".
Last edited by Feaelin on Sat Jun 19, 2021 3:22 pm, edited 2 times in total.

Feaelin
Manual Inserter
Manual Inserter
Posts: 4
Joined: Sat Jun 19, 2021 1:44 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Feaelin »

eradicator wrote:
Fri Jun 18, 2021 3:12 pm
Muche wrote:
Fri Jun 18, 2021 3:03 pm
eradicator wrote:
Fri Jun 18, 2021 2:16 pm
The dependencies aren't explicitly written down anywhere, they're just implicit in the order that tests are run.
I've read that an advantage of randomized test order is it could reveal hidden inter-test dependencies (e.g. a later test doesn't set the test environment properly and "relies" on an earlier test to do it).
Well, i guess you could split it into blocks? Ones that depend on each other and are tested in order, and the ones that don't depend and are tested shuffled? My amateurish self-made test environment doesn't really support randomization :D.
In addition to revealing that a given test depends on the setup of another test, random test order execution can also reveal implementation code that's retaining state when it shouldn't be. Tests that execute in order, and have hidden interdependence will mask unintended stateful behavior.

If you're finding yourself wanting to "manage" which test suites are being run together, that may signal that the implementation code isn't decomposed optimally. I'd look for code that is related but is spread out and code that is together that isn't actually closely related. Moving related code together and separating out unrelated code will (over time) reduce the number of times that making changes results in many tests failing.

Feaelin
Manual Inserter
Manual Inserter
Posts: 4
Joined: Sat Jun 19, 2021 1:44 pm
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by Feaelin »

fbo wrote:
Fri Jun 18, 2021 7:03 pm
TDD is great if your management support it and sets timelines accordingly.
Management should be setting timelines based on the engineer recommendations. Part of our responsibilities as engineers is to include _all_ of the effort in our estimation. The tests should be part of the engineer's estimate.

Also, once one has become practiced with TDD, it is often faster than programming without it. It reduces what I call "flailing" -- where one endlessly fiddles with the implementation to get it to work. It also shrinks execution time of the "make a change does it work" cycle, because with the tests in place you already know that the change didn't break the other functionality, provided of course, the other tests successfully cover that functionality.

We perceive TDD as being slower but it very often is faster, even at the micro-level. It is definitely faster at the product level due to the reduction of bugs going into the released version of the project.
While programming is about computers, management is about people.
Actually, programming is about people too. It is:
  • People (engineers) trying to communicate with a machine (easy)
  • Engineers trying to communicate with engineers in the future by writing understandable code (hard)
  • Engineers trying to teach machines to communicate with the users of the software (hard)
At its core, software engineering is about communication with several, very different, audiences.

And you need more disciplined programmers, because TDD is tedious:
- you're in fact doing double the work in one go (code & test) instead of the usual phases
- Instead of "yay, I did it right", after a while it feels more like "well, i did it - Again"
It is only tedious when you're new to it. Once one gets into the rhythm of it, it can actually be more gratifying than regular programming. With TDD, when the tests are targeted well, you get a hit of dopamine with each "green" test. Once you hit flow with TDD, it's intensely joyful.

Like a lot of things, one won't get that feeling when first starting out.
That's why you need to find a solution to break the workload down and also give people a feeling of success. Maybe intermix 'productive' phases using TDD and 'explorative' phases to try things out. The resulting mockups could be used as blueprints for later test cases.
There's value in what we call a "spike" where I work. A "spike" is an explorative / R&D type effort to see if something is even feasible. For a spike, someone will simply throw code at a problem until they've gotten a feel for the problem space and how to approach it. At that point, we either toss the code, or start a fresh branch, and implement a solution using TDD.

Something I've seen over and over again is that while the spike results in a working solution, invariably, when I re-do it using TDD, I discover that there were scenarios I didn't consider during the spike. This is because TDD creates a clean separation of thought about the possible scenarios. Often, one will identify cases that you wouldn't have otherwise.

User avatar
NotRexButCaesar
Smart Inserter
Smart Inserter
Posts: 1058
Joined: Sun Feb 16, 2020 12:47 am
Contact:

Re: Friday Facts #366 - The only way to go fast, is to go well!

Post by NotRexButCaesar »

Feaelin wrote:
Sat Jun 19, 2021 3:20 pm
[…]
Please edit the original post to add more content instead of creating multiple posts in a row.
: Alea jacta est. Determine what you intend to accomplish with an action before execution.
Have you ever heard the gospel? Most have not.

Post Reply

Return to “News”