This is such a shallow view of the topic. If you do good software engineering, all your programs also make great APIs for their problem domain, and there is only a few new things you need to learn about deploying them more widely if you fancy doing so. Making a great API is simply writing a great program, and here is what you have to go through to make one:

- You identify the best set of data and the best abstractions to represent whatever your program is modelling. This is a tricky conceptual part that often requires deep thought and sometimes several rewrites. You can represent a point in space as a triple of separate coordinates, but you will collapse under the mess of details if you try to build a 3D engine this way, and keep in mind this representation was already a revolution in thinking over the approach of the ancient Greeks, one that took mankind more than a thousand years and kickstarted calculus and then modern physics.

Good abstractions can simplify thinking so much as to allow doing completely new things, but they can also be very hard to find. I think linear algebra, which is what makes the 3d engine doable, is one of the more beautiful examples of this and one that should be studied carefully by prospective software engineers. It also isn't something that only comes up doing very sophisticated things, in the most mundane applications choosing a different set of variables to represent state might produce a revolutionary simplification. If you fail to do this step well, there is no way you are going to produce a good program, and frequently you won't produce nothing at all. An API that exposes wrong abstractions will be clunky to use no matter what else you do.

- You identify the constraints that make the data meaningful. A point on the screen might be represented by integers: x and y describing the position, and r, g and b describing the colors, but not all tuples of 5 integers will be valid representations of a point on the screen.

- You specify a set of operations preserving those constraints. You choose the names for those operations carefully. You make the argument list easy to remember. You make the most common use case the default one. You think carefully about which information is useful enough to return. You respect the conventions of the domain you are operating in. You use consistent naming. You write tests making sure your operations don't violate those constraints. ..., this is a whole craft to learn.

- You look for the best distribution and division of this data and operations into individual classes/modules. This again takes a lot of experimentation. You never, ever, EVER mix the abstract model of the problem domain with the messy details of what goes on the screen, with database accesses, with external API calls etc.

All this is not a sequential process, the activities have to be done repeatedly and in various order and you almost always need several attempts. Identify data, find abstractions, apply constraints, find a way to ensure operations preserve constraints - everything that was done in software engineering revolves around this, aspect oriented programming, design by contract, TDD, structured programming, formal methods, OOP, FP, generic programming, ...

However you approach it, you not only want to end up with a correct program that does what you want it to do, but also with a clear language that expresses the problem you are solving. Then your program is easy to understand for you and for others, hence more likely to be bug-free, easy to maintain and modify, and easy to expose as an external API.

Read some classic papers:

http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/crite...

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.136...

http://www.cs.cornell.edu/courses/CS4860/2012fa/Supplemental...

And some modern classics:

http://www.stepanovpapers.com/

http://www.cs.ox.ac.uk/people/richard.bird/

Maybe we will live to see times when software engineering is a real profession...