From 712b151de6b66c2fefbfcd9006ebc21ea148c275 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 10 Jul 2018 23:26:26 +0100 Subject: [PATCH] Add start of 'Intro to Clean Architectures' and 'Automatic Unit Testing' chapters --- _data/links_en.yml | 20 +- en/chapters/mvc.md | 193 ++++++++++ en/chapters/unit_testing.md | 39 ++ static/mvc_diagram.svg | 691 ++++++++++++++++++++++++++++++++++++ 4 files changed, 939 insertions(+), 4 deletions(-) create mode 100644 en/chapters/mvc.md create mode 100644 en/chapters/unit_testing.md create mode 100644 static/mvc_diagram.svg diff --git a/_data/links_en.yml b/_data/links_en.yml index 4c7c11c..4695619 100644 --- a/_data/links_en.yml +++ b/_data/links_en.yml @@ -26,7 +26,7 @@ link: chapters/creating_textures.html - title: Node Drawtypes - description: Guide to all drawtypes, including node boxes/nodeboxes and mesh nodes. + description: Guide to all drawtypes, including node boxes/nodeboxes and mesh nodes. num: 5 link: chapters/node_drawtypes.html @@ -102,16 +102,28 @@ link: chapters/common_mistakes.html - title: Automatic Error Checking - description: Use LuaCheck to find errors + description: Use LuaCheck as a linter to find errors num: 20 link: chapters/luacheck.html -- title: Releasing a Mod +- title: Intro to Clean Architectures + description: Separate logic from data and API calls using the MVC pattern - Model View Controller. num: 21 + link: chapters/mvc.html + +- title: Automatic Unit Testing + description: Write tests for your mods using Busted. + num: 22 + link: chapters/unit_testing.html + +- hr: true + +- title: Releasing a Mod + num: 23 link: chapters/releasing.html - title: More Resources - num: 22 + num: 24 link: chapters/readmore.html - hr: true diff --git a/en/chapters/mvc.md b/en/chapters/mvc.md new file mode 100644 index 0000000..4ad7874 --- /dev/null +++ b/en/chapters/mvc.md @@ -0,0 +1,193 @@ +--- +title: Intro to Clean Architectures +layout: default +root: ../../ +--- + +## Introduction + +Once your mod reaches a respectable size, you'll find it harder and harder to +keep the code clean and free of bugs. This is an especially big problem when using +a dynamically typed language like Lua, given that the compiler gives you very little +compiler-time help when it comes to things like making sure that types are used correctly. + +This chapter covers important concepts needed to keep your code clean, +and common design patterns to achieve that. Please note that this chapter isn't +meant to be prescriptive, but to instead give you an idea of the possibilities. +There is no one good way of designing a mod, and good mod design is very subjective. + +* [Cohesion, Coupling, and Separation of Concerns](#cohesion-coupling-and-separation-of-concerns) +* [Model-View-Controller](#model-view-controller) +* [API-View](#api-view) + + +## Cohesion, Coupling, and Separation of Concerns + +Without any planning, a programming project will tend to gradually descend into +spaghetti code. Spaghetti code is characterised by a lack of structure - all the +code is thrown in together with no clear boundaries. This ultimately makes a +project completely unmaintainable, ending in its abandonment. + +The opposite of this is to design your project as a collection of interacting +smaller programs or areas of code. + +> Inside every large program, there is a small program trying to get out. +> +> --C.A.R. Hoare + +This should be done in such a way that you achieve Separation of Concerns - +each area should be distinct and address a separate need or concern. + +These programs/areas should have the following two properties: + +* **High Cohesion** - the area should be closely/tightly related. +* **Low Coupling** - keep dependencies between areas as low as possible, and avoid +relying on internal implementations. It's a very good idea to make sure you have +a low amount of coupling, as this means that changing the APIs of certain areas +will be more feasible. + +Note that these apply both when thinking about the relationship between mods, +and the relationship between areas inside a mod. In both cases you should try +to get high cohesion and low coupling. + + +## Model-View-Controller + +In the next chapter we will discuss how to automatically test your code, and one +of the problems we will have is how to separate your logic +(calculations, what should be done) from API calls (`minetest.*`, other mods) +as much as possible. + +One way to do this is to think about: + +* What **data** you have. +* What **actions** you can take with this data. +* How **events** (ie: formspec, punches, etc) trigger these actions, and how + these actions cause things to happen in the engine. + +Let's take an example of a land protection mod. The data you have is the areas +and any associated meta data. The actions you can take are `create`, `edit`, or +`delete`. The events that trigger these actions are chat commands and formspec +receive fields. These are 3 areas can usually be separated pretty well. + +In your tests, you will be able to make sure that an action when triggered does the right thing +to the data, but you won't need to test that an event calls an action (as this +would require using the Minetest API, and this area of code should be made as +small as possible anyway.) + +You should write your data representation using Pure Lua. "Pure" in this context +means that the functions could run outside of Minetest - none of the engine's +functions are called. + +{% highlight lua %} +-- Data +function land.create(name, area_name) + land.lands[aname] = { + name = area_name, + owner = name, + -- more stuff + } +end + +function land.get_by_name(area_name) + return land.lands[area_name] +end +{% endhighlight %} + +Your actions should also be pure, however calling other functions is more +acceptable. + +{% highlight lua %} +-- Controller +function land.handle_create_submit(name, area_name) + -- process stuff (ie: check for overlaps, check quotas, check permissions) + + land.create(name, area_name) +end + +function land.handle_creation_request(name) + -- This is a bad example, as explained later + land.show_create_formspec(name) +end +{% endhighlight %} + +Your event handlers will have to interact with the Minetest API. You should keep +the amount of calculations to a minimum, as you won't be able to test this area +very easily. + +{% highlight lua %} +-- View +function land.show_create_formspec(name) + -- Note how there's no complex calculations here! + return [[ + size[4,3] + label[1,0;This is an example] + field[0,1;3,1;area_name;] + button_exit[0,2;1,1;exit;Exit] + ]] +end + +minetest.register_chatcommand("/land", { + privs = { land = true }, + func = function(name) + land.handle_creation_request(name) + end, +}) + +minetest.register_on_player_receive_fields(function() + +end) +{% endhighlight %} + +The above is the Model-View-Controller pattern. The model is a collection of data +with minimal functions. The view is a collection of functions which listen to +events and pass it to the controller, and also receives calls from the controller to +do something with the Minetest API. The controller is where the decisions and +most of the calculations are made. + +The controller should have no knowledge about the Minetest API - notice how +there are no Minetest calls or any view functions that resemble them. +You should *NOT* have a function like `view.hud_add(player, def)`. +Instead the view defines some actions the controller can tell the view to do, +like `view.add_hud(info)` where info is a value or table which doesn't relate +to the Minetest API at all. + +
+ Diagram showing a centered text element +
+ +It is important that each area only communicates with its direct neighbours, +as shown above, in order to reduce how much you needs to change if you modify +an area's internals or externals. For example, to change the formspec you +would only need to edit the view. To change the view API, you would only need to +change the view and the controller, but not the model at all. + +In practice, this design is rarely used because of the increased complexity +and because it doesn't give many benefits for most types of mods. Instead, +you tend to see a lot more of a less formal and strict kind of design - +varients of the API-View. + + +## API-View + +In an ideal world, you'd have the above 3 areas perfectly separated with all +events going into the controller before going back to the normal view. But +this isn't the real world. A good half-way house is to reduce the mod into 2 +parts: + +* **API** - what was the model and controller. There should be no uses of + `minetest.` here. +* **View** - the view as before. It's a good idea to structure this into separate + files for each type of event. + +rubenwardy's [crafting mod](https://github.com/rubenwardy/crafting) follows +this design. `api.lua` is almost all pure Lua functions handling the data +storage and controller-style calculations. `gui.lua` and `async_crafter.lua` +are views for each type of thing. + +Separating the mod like this means that you can very easily test the API part, +as it doesn't use any Minetest APIs - as shown in the +[next chapter](unit_testing.html) and seen in the crafting mod. diff --git a/en/chapters/unit_testing.md b/en/chapters/unit_testing.md new file mode 100644 index 0000000..4e2c9b3 --- /dev/null +++ b/en/chapters/unit_testing.md @@ -0,0 +1,39 @@ +--- +title: Automatic Unit Testing +layout: default +root: ../../ +--- + +## Introduction + +Unit tests are an essential tool in proving and resuring yourself that your code +is correct. This chapter will show you how to write tests for Minetest mods and +games using busted, and how to structure your code to make this easier - Writing +unit tests for functions where you call Minetest functions is quite difficult, +but luckily [in the previous chapter](mvc.html), we discussed how to make your +code avoid this. + +* [Installing Busted](#installing-busted) + * [Windows](#windows) + * [Linux](#linux) + + +## Installing Busted + +### Windows + +*Todo. No one cares about windows, right?* + +### Linux + +First you'll need to install LuaRocks: + + sudo apt install luarocks + +You can then install Busted globally: + + sudo luarocks install busted + +Check that it's installed with the following command: + + busted --version diff --git a/static/mvc_diagram.svg b/static/mvc_diagram.svg new file mode 100644 index 0000000..9d9face --- /dev/null +++ b/static/mvc_diagram.svg @@ -0,0 +1,691 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + Controller + Model + View + Testable + Your Mod + + + + + + + + + Mod1 + + Mod2 + + + + + + + + + + + + + + + + + + + + + + + + + + + Pure + Dirty + + +