2

I am still an beginner/intermediate programmer at best so apologies if I misuse terminology.

I work with SCADA software for my job, specifically Ignition by Inductive Automation. It's made for rapid application development, and all you really need to know is that it's built upon java, so what I end up using in practice is jython 2.7.

The application I work on strictly a database CRUD application for tracking office paperwork, invoices, sales orders, etc. I realized after a while the structure of each of these forms is essentially the same set of steps, with slightly different implementations at different steps.

For creating a new invoice for instance, I get some data off the screen, I validate any values that might not be good (ex: a age that is negative) and throw an error if there is one, if not, I sanitize the data if needed (sometimes due to poor GUI design ie changing a string date to a date object), and then I run a named query with the parameters to make an insert inside of a db transaction (and running any other database queries that are related to the invoice inside the same db transaction in case something goes wrong I rollback). If everything was successful, I log a message to a history thread for the object, or if not, I log the error to a server and tell the user something went wrong.

Now some things, this abstract class (think this is the right term) Form takes care of a few things behind the scenes - it automatically figures out where the subclasses named queries should be - for invoices it will look in a folder called forms/invoices for named queries called create, update, and delete for the appropriate actions. The Form class also has a generic way of running subclasses create/update/delete statements within a the database transaction, and I keep all error parsing inside the abstract class Form so that every subclasses handles db errors or validation errors the same way.

As an example, for creating a new record, this is what my Form class looks like

def create(self, newData):
    self.logger.info("running create")
    # Have to reset this here in case of multiple creates, don't want a false positive on the second item
    self.resetResults()
    self.newData = newData
    self.modifyNew()
    self.validateNew()
    self.sanitizeNew()
    self.inputDefaultValues()
    # Database transaction/error catching - but only on the top level
    if not self.middleOfTransaction:
        self.tryAndFailDBOperationGracefully("inserting")
    else:
        self._create()
    # TODO - decide - if we should even update the UI if there's an error after the db stuff.  Leave for now
    self.updateUIAfterInsert()

Some of these things the user needs to implement if they want - modifyNew, validateNew, sanitizeNew, and updateUIAfterInsert are all pass in the Form class and can be skipped if not used but otherwise should be implemented if you want them to run. Other things like resetResults, inputDefaultValues, tryAndFailDBOperationGraceflly, _create are all things subclasses should not overwrite.

So how can I document something like this, what is the right way to inform someone via a UML diagram that if you want to use my Form class, here's how it works, and here's what you need to do on your end to subclass it properly? Also related - is this Form object an abstract class or is it a Interface? I am confused about the difference.

5
  • 2
    Basically, you write down everything you've just said (albeit in a slightly more organized way) as documentation text; UML isn't particularly helful in this specific case. Describe what the class is for (what it does in general terms, how it fits or what its role is in your system), describe how people can make use of your class, and then describe the methods that the other devs might be interested in. Have a section on extension points: document what methods can be overridden, what are their inputs, outputs, mention any important non-obvious preconditions/postconditions that need to be met. Jun 16 at 16:35
  • 2
    Regarding the level of detail: imagine you're writing for someone who's on your team (so, a programmer who is familiar with the project), but hasn't really used your class before and doesn't really know much about it, or how to properly extend it. BTW, that person could be future you (in two weeks, or 3 months, or...), when you've forgotten what that code even was. Jun 16 at 16:38
  • BTW, that person could be future you (in two weeks, or 3 months, or...), when you've forgotten what that code even was. In all likelihood it will be me 6 months from now lo. I appreciate your comments. So perhaps breaking it down onto each of the main things it can do - create a record, update a record, delete a record, save a file (if there's a report associated with it), and getting email presets regarding the record (used to populate email screens). A breakdown on the functionality - and inside each of those, what the subclass is responsible for to make it work. That sound right? Jun 16 at 18:05
  • 1
    Sounds about right. You know how, sometimes, when you're confronted with unfamiliar code, you kind of stare at it, and you pretty much understand what each line is doing, but it's not immediately obvious what the function (or the class) as a whole is trying to achieve and why - until it suddenly clicks, maybe after you've checked the surrounding/calling code, and played with the running application a bit, etc? And you're like: Oooh, that's why! Well, put that in the documentation, don't get too caught up in details. Give your readers the gist first; they can figure out the details later. Jun 16 at 20:22
  • @BrianKarabinchak Just curious about the three method (modify, validate and sanitize) - are there also other usages of those methods in the Form class? If, so they always are called in this group of 3 and in that order?
    – sevenforce
    Jun 19 at 16:06

3 Answers 3

3

UML class diagrams answer three questions well:

  • What knows about what? (Note the lines)
  • What are these things called? (Note the class names)
  • How do you talk to these things? (Note the method signatures)

If you’re trying to explain something that isn’t the answer to those questions then use something else to explain.

Note: Abstract classes and Interfaces are different mainly in those languages that use Interface as a keyword. Typically this means they didn’t implement multiple inheritance correctly the first time around and hacked it into their language later by adding the Interface keyword.

A lower case i, “interface”, simply describes how you can talk to it. Completely ignores how it works.

2
  • Ok so going off of this 1) what knows about what? (Note the lines) - this helped a lot. I think including at least a few of my subclasses would greatly improve understanding of what it's doing and why. 2) So just the class names - makes sense. 3) They don't really talk. I guess my setup is pretty simple in that it's all inheritance, all my subclasses just inherit from this Form class. Perhaps the form Class should be broken up a bit and itself be a composition of other simpler classes but for now I guess it's kind of a class that is doing too much. Appreciate the explanation of interface. Jun 17 at 13:33
  • 3) if you know about something, and how to talk to it, you can talk to it. You can use the lines, and the types a class knows, to show what a class (and it’s objects) can talk to. That is, “send messages” in OOP speak. In this way a class diagram can give a rough feel for how information flows through the system. It’s more than just a structure. Jun 17 at 13:57
1

Communicate the big picture

To communicate the big picture about a set of classes that are supposed to work together, you may consider the following:

  • An UML class diagram (structure), to explain the involved classes, their responsibilities, and their relationships. In your case, you would typically document the operations (UML term for methods) that each class has to provide for your interaction to work.
  • An UML sequence diagram (dynamics), to explain how involved objects of these classes interact by creating each other, and communicating (i.e. calling their operations). Keep diagrams simple, you may make several such diagram, each explaining a specific scenario.

These two simple diagrams help to grasp at a glance the classes involved and how they work together.

Improve your customer/developer's experience

It's not graphical overview vs. self-explanatory code: it's both together. The diagrams are the map that tells where to look for the treasure. The users can then navigate quickly to the related parts with their to find more information. But without the map, the customers might take a lot longer to figure out how all the parts fit together.

To maximise the experience:

  • Keep it lean: show only on the key elements of your design. Don't put all attributes and methods; this would only lead to unreadable diagrams that might frighten away your audience ;-)
  • Ideally, you'd use the the diagrams together with a short explanatory text on the landing page of your documentation,
  • You may provide also an API documentation generated automatically from your comments.

If you're using python, you can give a quick trial at PyReverse (it's part of Pylint, a tool you should consider if you're making mission critical SCADA systems) : it generates automatically some UML class diagrams. But usually, for a design overview, you'll be as fast drawing it from scratch.

Abstract class or not?

Form is a class since it provides an implementation for some methods. It's probably not an abstract class by the book, if it can be instantiated.

Nevertheless, nothing prevents you to show it as abstract on the diagram, to underline that a subclass must be created to make it work. Personally, I'd show is as a normal class and would show one subclass in the diagram that shows the operation that should/could be overridden.

1

Documentation for programmers

First off, as you said you want to provide documentation for developers using this class. There is some discussion about documenting/commenting code, that you can find eg. here:


Step towards self-documenting code

One thing that goes in the direction of self-documenting code is the following: You mentioned a set of methods that should not be overridden - resetResults, inputDefaultValues, etc. If that always should hold, you could change the methods to follow the Python convention for private access method: in prefixing them with two underscores, having then __resetResults, __inputDefaultValues, and so on.


You have applied a Design Pattern (which includes existing UML diagrams)

Some additional context to your approach: With the Form class you are in fact following one of the classic design patterns by Gamma et al: the Template Method pattern.

So, I would not create any additional UML diagram, but instead just reference the pattern.

The documentation itself, about the available methods, etc., could just by added to the Form class itself as a Python docstring.


Additional info

For completeness, in Python since version 2.6 there is in fact a sort of abstract method support with the ABC module. Though that could be from a complexity standpoint counterproductive for your use case.

To have sort of a mandatory abstract method in Python you can always just raise a NonImplementedError in the base class method.

2
  • Interfaces have no code, therefore they cannot have self-documenting code. And the code would self- describe what it does, which isn't helpful because you need to know what other code following the same interface does.
    – gnasher729
    Jun 20 at 16:50
  • 1) Thank you for the tip about __resetResults - having a convention like that would help know which is to be overwritten and what is not to be. 2) I never heard of the Template Method Pattern - I'm glad what I am doing is an established pattern and not an established anti-pattern lol. However, this does give me a a new rabbit hole to go down so I greatly appreciate it! Jun 21 at 12:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.