Cut the process/machine operation into small manageable chunks, then program those chunks in order. I try to have my program's organization follow the actual process flow, f. ex. stations in the IRL order in the program. Try to maintain the same overall structure in each of your programs. I work for an SI and I often do one-off machines or process sections, but my base structure remains similar throughout each different PLC I do. If we get repeat business it's way easier for maintenance staff to transition from one machine to another and they get a sense of the logic flow and where everything is even if they've never seen that particular program before.
The definition of a "chunk" may vary, especially when it comes to the process logic part. For instance, for me an entire valve matrix might be a "chunk". Someone else may separate the different pipelines instead into "chunks". I don't think there are real true answers as long as what is a "chunk" is constant throughout.
That's optional, but I like to restrict the scope of my variables to only as much as I need. That means using program tags or the equivalent. If I need "chunks" to talk to each other I will scope interface tags appropriately. I think of it like I/O mapping. If I need to change an I/O point I just have to change it at one place. Equivalently, if I have to change the logic of a "chunk" I just change it there and because of the interface I don't have to run around the entire program changing stuff at different places. You don't really need to scope tags to do this but it helps keep the discipline.
Another optional tip, I like to have all my tasks periodic, especially the process task. The reason is that it keeps time-dependent logic with the rest of the logic, inside their chunks. I find that much clearer to follow than having to jump to a separate periodic task and find the bit of logic I need.
I don't have anything against latches and unlatches, but if you use them latch at one place and unlatch at one place, preferably in close proximity.
I like to place a "ChangeLog" ST subroutine that's just a huge comment that tracks changes. It never gets used except by me, but if it *did*, it'd really help tracking who did what where and for what reason.
For the love of your preferred deity, include a way, any way, for the operator to see conditions that interlock an action. No one should need to go into the program to tell the operator "yeah you can't do this because this swing elbow is not detected".
And if you make live modifications, erase your programmer bits and the old logic when you know it works. Nothing worse than seeing *years* worth of logic modifications onto a single rung. You've got a toggled bit JohnF_20160412 cancelled by TomC_20180718 and the old 2014 logic is still there!