Monday, 11 June 2018

Learning Python by Writing An Application (2)

I haven’t written high-security web applications that link to secure back-end databases, so I’m not talking about those. I’m talking about regular, runs-on-your-computer applications with a UI.

Real programmers know that their command-line utility never hangs and has a try...else to make it stop at the first sign of trouble, so they don’t need it to talk to them. I hate it when a program sits there seemingly doing nothing, I want some feedback. There’s a trade-off between speed and user dialogue. No, not progress bars. Progress bars require sizing the job first, so that the progress can be shown as a percentage of the total. Ever noticed that if the number of files you’re copying is large enough, Windows suddenly takes an age doing some mysterious preparation? That’s what I want to avoid. The only way round that is a running commentary. I don’t want much, maybe something like

Scanned N directories, copied M files to (destination)

updated every 10 files. When it’s syncing, I want to see

Scanned N directories, deleted M files from (destination)

(Real Java Programmers would write a log file of every action, along with the time it happened. I’m so glad I’m not a Real Java Programmer.)

Tkinter is notorious for updating at random. I don’t think this is Python’s fault, but the way the OS and hardware parcel out the tasks. So by the time Tkinter gets a moment to display the first confirmation, the whole task might have been done. To get round this, I called my first thread! I’m a VBA-basher in my day job so threading is something exotic and mysterious, like (insert inappropriate analogy here). It solved the Tkinter delay problem nicely.

My program has a button to choose the source directory, another to choose the target, one to Start and one to say Stop. Think about the UI logic. The Stop button should not be active unless there’s copying or syncing going on. The Run button should not be active unless there are valid source and destination directories.

What happens if I copy a file and there isn’t enough spare space on the target disc? At the very least I’m going to get an error message, and so I’d need to handle that. Or I could test for spare space and stop copying when I reach some kind of limit. That’s what I did: read in the spare space at the start, store that in a variable, subtract the copied file size from that value, and stop if the file to be copied is within some limit of the calculated space. Using the constant cuts down on filesystem calls.

What happens when there’s already a file with the name of the being copied BUT the size is different? The program needs to add a suffix to the filename, but then it has to test that that filename doesn’t exist, which sounds like a loop. And don’t forget to put a numerical limit on the number of attempts just in case someone used a silly file-naming convention.

A lot of programming is testing for and resolving edge-cases. For instance, the destination directory should not be the source directory, nor a subdirectory of the source directory. I got the first condition on the first pass, but only thought of the second when writing this. (Documentation reveals many improvements.)

This is all exception and edge-case handling.

In my experience, the actual business functionality, the code that does what you want done, is about ten per cent of the total code. Variable declarations and syntactic seasoning is about another ten per cent. Setting up, controlling and making the internal logic of the UI consistent, will be about twenty-five percent, depending on how complex it is.

All the rest is handling edge cases, data weirdness, potential errors, null values, and other stuff that might trip up the program.

I’m not kidding.

If half your code isn’t testing that things that need to be there for the next step to work are actually there, or for missing values, forcing stuff to be strings when the function expects strings, putting limits on loops in case something you have never thought could happen does happen and the terminating condition is never reached... if you’re not doing all that, and you’re not writing in sensible defaults for when the user has to make a choice, and you’re not testing for when the user does something really dumb because they’re distracted, or walks away for five minutes, or all those other things you don’t want users to do because then your life gets difficult... then you’re going to produce a program that people are not going to use twice.

The difference between a professional and an amateur is that the professional writes all the edge-case and exception handling they can think of. An amateur thinks ‘Well, the user will just have to...’.

Don’t forget to put in a default drive for the source and destination directories, or the getdirectory() function will dump the user deep down a file tree it will take a boring time to get out of and get to where the user should have been in the first place.

Programming isn’t about writing code to do stuff. That’s the easy part. It’s about writing code that makes sure the user and the computer don’t get in the way of the stuff getting done.

No comments:

Post a Comment