In general when I start a new project I divide the issues up into several boxes: PD, DS, SI, HI, and NY.
PD: Problem Domain
This is the code for the actual functionality. If you're calculating artillery trajectories, this is where you put your kinematics code. If you're writing a chess engine, this is where you put your move tree pruning. If you're writing control firmware for ABS systems, this is where you link your sensors to your actuators via your PID controller. (This happens to also be the code that is most likely going to require few to no changes even if changing platforms.)
DS: Data Storage
This is where you map the data needed by the PD code into how you plan to persist it. This is where you have your flat file reader, your database integration layer, your file system handling, etc. It may be mildly system-specific: you may need minor changes in file systems when moving from Windows to Linux, say, or from SQL Server to MySQL, but the code here will be generally stable as long as you don't profoundly change your persistence model.
SI: System Integration
This is where you toss the stuff that's not related to data storage but is variable across systems. Your concurrency mechanism maps from your model to the native model here. Your network interaction layers are in here. Even your I/O primitives could be found here depending on the precise nature of your PD.
HI: Human Interaction
Whether command line or GUI or VR or whatever else is dreamed up, you put this into a separate box. If your PD code has any code related to human interaction in it, you've done fucked up and you've locked yourself into a single mode of interaction for no good reason. (Most software, sadly, has done fucked up in this regard.)
NY: Not Yet
This is the box where features that would be cool but that you're not going to put into this release go. Why don't you just toss them out and deal with them later? Because knowing that you're going to put a feature in will have you thinking of how you'll put it in and leave you room in your design for that later feature expansion. If you just toss out the NY features without thinking about how they'd fit in in the future you're opening a can of whoop-ass on your own code base.
Now, not all software has all five boxes filled. An embedded sensor monitor, for example, may not have an HI component at all: its controller will have that. And your HI and PD might be the same thing if your software is, say, a GUI framework library. But in general an overwhelming majority of software would be well-served by using this simple model.
And here's the thing.
Even beginners and hobbyists would be well-served by doing this boxing. Because separation of concerns makes for simpler code bases, not more complicated ones. But it does mean you have to think before you type. Which is the major distinction between "programming" and "coding".
This, @Ashen-Shugar, is incidentally why I don't think there's actually a clash between supporting the CONFIG.SYS
-lovers and the WIMP
-lovers in properly-constructed software. The issue isn't that it's impossible to support CLI and GUI and whatever in the same program or program suite. It's that most software is written by people who can't imagine that it's even possible.