[Tutor] Multiple front-ends in project

dn PythonList at DancesWithMice.info
Wed Jun 28 00:58:46 EDT 2023


On 28/06/2023 05.04, trent shipley wrote:
> Middle posting

Responses interposed
(posh talk!)


> On Sun, Jun 25, 2023 at 5:34 PM dn via Tutor <tutor at python.org 
> <mailto:tutor at python.org>> wrote:
> 
>     On 24/06/2023 12.50, Alan Gauld via Tutor wrote:
>      > On 23/06/2023 18:11, trent shipley wrote:
>      >> I have written the simple core logic for a dice rolling
>     program.  It is In
>      >> ./app/core.  I am planning to add more complex helper classes in
>      >> separate files to do more complex things like rolling different
>     kinds of
>      >> dice in one throw, or exploding dice.
>      >>
>      >> I plan four interfaces, two CLI, and two GUI.
> 
>     Further to @Alan's analysis, (although not being aware of your level of
>     ComSc/DataSc expertise) I regularly remind/put the 'circles' diagram in
>     front of trainees
>     (https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg <https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg>)
> 
> 
> The diagram was very useful, especially the little diagram in the lower 
> right corner.

When looking at the code, does EVERY class and function fit (only) into 
one of those 'circles'?

The same question is probably applicable to each fitting into either one 
of the two CLI or one of the two GUI 'front-ends' OR is part of the 
actual dice-simulation 'core code'?

If not, recommend some refactoring...


>     This illustrates that the code fulfilling the purposes of the outer
>     'circles' needs to know (a limited amount of) what's going-on 'inside',
>     but the inner-circle(s) don't need to know where data is coming-from or
>     going-to! (paralleling @Alan's "MVC" ideas of 'separation')
> 
> 
> I have an unfortunate habit of designing bottom-up despite having been 
> taught to favor doing it the other way around.

No!

Bottom-up can't be "design". One can't draw a jig-saw puzzle's picture 
by painting on successive pieces before they are assembled into the whole!

In this case, you can identify your four front-ends, and a 
computational-core. Thereafter you could ignore all of those components 
except the last, and design that in more detail - only coming back to 
designing the others after implementation of the core.
(but that's not "bottom-up")

You can break-down the details of the core, 'layer by layer', each more 
precise than the other, until you're ready to code - and can imagine how 
all of that (piece of the) code will fit together.

Coding can then be a bottom-up process:
- a function to roll a single, six-sided (traditional) die
- expand to roll the die multiple times
- enhance to a variable number of 'sides'
- upgrade to consider multiple dice
...

That is an iterative process.
Even better if, each time, you can add controlling functions which fit 
around the basic process!

If you can keep the code modular (which in Python usually means classes 
and functions - with modules, perhaps when start adding the four 
front-ends into the picture) this will make iteration easier. Also, 
testing as you go solves the problem of 'where did that error actually 
arise' (cf where it became manifest).


OK, I suspect that this was a project which started-out as a topic for 
learning (some) Python. In which case, 'design' was not front-of-mind, 
and something, perhaps the random library, was the focus of attention. 
No criticism!

There comes a time though, when the aggregation of various ideas, 
inspirations, desires, imaginings, and experiments (bottom-up) starts to 
smell like 'warts have been grafted onto the back-side of carbuncles'. 
In which case, starting with a fresh sheet of paper/wiped whiteboard, 
and re-imagining a solution is likely the best path forward*. By this 
stage, one's Python knowledge has increased, and one's domain-expertise 
has grown. Consequently a 'design' is now possible (if not, necessary) 
whereas it was not before - or not the primary mission earlier.

Either way, the design becomes a top-down view, starting with (perhaps) 
a single sentence, and becoming more and more detailed as the 'layers' 
become more precise.

* which is not to say that you won't re-use chunks of code!


Accordingly, I start with 'design' and follow the principle of stepwise 
decomposition. However, once coding starts, I work function-by-function, 
class-by-class (module-by-module!) and testing each unit as I go. I know 
which unit to start with, and which to code 'next' (and so-on) because 
'the design' acts as a road-map. That process however, is very much 
bottom-up.

Sometimes, when working at the code-face, it can be difficult to keep 
in-mind how this 'unit' plugs into the application as a whole. This is 
where 'design' helps one stay on the straight-and-narrow!

Another aspect of 'design' (and bringing-up YAGNI/reining-in our 
native-enthusiasm) is that if one starts with an "MVP", then it helps us 
to follow the maxim "make it work before you make it 'better'!". To 
borrow from Fred Brooks (Architect of IBM's System/360 project), 
constraints can be our friends!


Are you getting the feeling that no 'one size' will fit all?


>     If interested, please see-also theories such as "Separation of
>     Concerns"
>     and the "SOLID Principles", specifically the "Single Responsibility
>     Principle". In short, if the description of the work performed by a
>     single unit of code (eg function, class, ...) features the word "and",
>     then that code may contain a 'gotcha' for exercises such as this!
> 
> 
>     Accordingly, the code which actually includes a call to the
>     random-library does not need to know if the request has come from a
>     user
>     using a GUI or the CLI. Yes, other 'outer' logic can take care of
>     that -
>     and ensure that a request from the GUI will return the results in
>     similar fashion rather than printing to the 'command-line'.
> 
>     Considering the 'center' in a bit more detail: what data does the 'core
>     logic' require from its "External Interface" (regardless of which one)?
> 
>     This may include:
>     - how many sides to the die/dice
>     - how many dice
>     - home many rolls
>     ...
> 
> Indeed.
> 
>     This is the "API" (Application Programming Interface). More
>     prosaically,
>     it is probably the 'signature' of one of the existing functions in the
>     system. (which is another way of getting rid of the high-falutin
>     terminology)
> 
> 
> Yes the dice rolling logic is accessible and is what middle layers would 
> access.
> 
>     Once you've identified ("finalised") this, then turn your attention to
>     the GUI and CLI components. The 'input' part of these must be built to
>     be as pretty and/or as workable as is appropriate, but ultimately must
>     provide the information required by the (above) API/signature.
> 
> 
> Is it OK to work on this iteratively, rather than trying to pin down the 
> needed 'business' logic and functions in one go?

Sure, but...

If the business-logic is updated 'internally' - for example swapping one 
PRNG for another, then no-problem.

If you implement (say) a routine which deals with six-sided dice, and 
then build the four 'front-ends', everything will be fine-and-dandy. 
However, if you now enable multi-sided dice, not only does the 'business 
logic' have to be updated, but so too must each of the front-ends. Thus, 
the cost of change is much higher. However, you have an MVP up-and-running.

Now, if you factor-in the idea that until users can actually see 
'something' (today marketing-folk use the word "experience") they won't 
know that they want something different, bigger, brighter, better, 
faster, in pink, with racing-stripes, ...

Once again, I'll preach the principles of 'design', of modular 
programming, and "structured programming". If your solution has been 
built with 'change' in-mind - which is somewhat similar to building-in 
every little option, except that you don't actually, you only build what 
is there, with the idea that extensions may/will come at some future stage.

Flexibility...
Open-Closed Principle...


>     The same, in-reverse, for output...
> 
> 
>     Referring back to the diagram. The die/dice is an "entity". The number
>     of sides per die is a "business rule" (the 'business' (ahem) of
>     statistical distribution). The "Use Cases" are the varieties of
>     simulations you'd like the dice to be used to perform. The
>     Web/UI/External Interfaces encompass many of your existing
>     explanations/intent.
> 
> 
>     Have enjoyed considering the question. Some additional comment (FWIW):
> 
>     Further to @Alan's "waffle", may I recommend the "YAGNI Principle", ie
>     don't 'put stuff in' until you actually receive a solid use-case for it
>     (in my case: I don't do things until the client has agreed to pay for
>     them). Just because it might be useful - or might be fun to code-up, is
>     not a solid reason (it *is* a reason (professional responsibility?) to
>     go back to the client and ask if (s)he agrees - *and* is prepared to
>     pay
>     for it!). Most of the time it represents a delay to 'delivery' because
>     You/They Ain't Going To Need It!
> 
> When I build software at work, I have a horrible problem with gold 
> plating, so KISS and YAGNI are real issues for me.  The killer app I 
> imagined was a dice roller for RPGs with a GUI front end I could 
> actually use and tinker with.  The CLI version I'm working on now may 
> not have too much utility, And the 'advanced' or 'scientific' versions 
> are solutions looking for consumers.

Professionalism includes discipline!

Isn't dice-rolling a fairly standard intro-to-writing-code in stats courses?
(was in mine - coins and dice illustrating random processes, counting, 
distributions, ...


>     Am also anticipating you will have to put in some 'rework'. Recommend
>     that you start with (unless you have them already) a set of tests (a
>     'testing framework'). This will 'prove' (in the logic/mathematical
>     sense) that the existing code works correctly. This is a 'stable
>     state'.
>     Now, when something is 'reworked' in order to separate the components
>     and create the API (between External Interfaces and the Entities and
>     Use
>     Cases/Controllers), the tests will continue to pass - or will fail and
>     thereby show that the 'new work' has introduced an error (which, one
>     way
>     or the other) will need 'fixing'! It seems like more (even,
>     unnecessary)
>     work up-front, but may pay greater dividends over time...
> 
> There are some tests.  They pass.  I don't trust them.  I've been an 
> SDET for functional testing of web and Windows GUIs, but have only very 
> recently needed to work with unit and parameter tests.

Here's another personal bias: TDD - and make sure the tests are 
worthwhile (what point otherwise?)

A potential client called for help today with a data-capture and Python 
problem - "it has suddenly stopped working/crashed". One of my early 
questions was 'what tests do you have?'. You can guess that answer...
(see also conversation 'here' about using modules to better 
locate/isolate steps in the application, and thus locate the sources of 
errors)

The 'good news' about unit-testing Python code is that you can use 
(straight-line code) Python, eg pytest. Compared with GUIs where one 
might have to also learn and use Selenium, or similar!


> Yesterday was Scala day, today is Python day.
> 
>     Looking forward to hearing how the project progresses...


-- 
Regards,
=dn



More information about the Tutor mailing list