|
Large Application Architecture |
Learn how to organize a real 50,000+ line Sheerpower application using %include files, modules, private routines, scoped routines, local routines, and a clear application structure.
@utilities.spinc
will always point to utilities.spinc in the same folder
as your running program. This ensures your code finds its files
reliably, without hardcoded paths.
Small programs can live in one source file. Large business applications cannot. Once an application grows past a few thousand lines, the main challenge is no longer writing individual statements. The challenge is organizing the program so it remains understandable, testable, and safe to change.
Sheerpower gives you the building blocks for this:
%include files, modules, private routines, scoped
routines, local routines, and clear routine boundaries. This
tutorial shows how to put those pieces together into a practical
architecture for a real 50,000+ line application.
Problem: A large application that starts as one file often becomes difficult to understand. Every change feels risky because data access, business rules, display logic, and helper routines are all mixed together.
Solution: Divide the application into clear source files
and modules. Use %include to assemble the application,
use routines to create stable boundaries, and keep internal helper
logic private whenever possible.
Efficiency: A well-organized Sheerpower application is easier for both humans and AI tools to modify. Each file has a clear job, and the compiler can still build the full application quickly.
Takeaway: Large Sheerpower applications should be organized around responsibility, not just around line count.
Application architecture is the plan for where things belong. It answers questions like:
The purpose is not to make the program look complicated. The purpose is to make the program easy to reason about when it becomes large.
A 50,000+ line application usually benefits from a simple folder layout. The exact names are not important. What matters is that the structure is predictable.
In a small program, the main source file may do everything. In a large application, the main program should act more like a conductor. It should assemble the application, initialize it, and hand control to the proper routines.
Notice what the main program does not contain. It does not contain detailed invoice calculations. It does not contain report formatting. It does not contain low-level table lookup code. Those details live in the appropriate modules.
Problem: When the main source file contains all of the application logic, every part of the program becomes connected to every other part.
Solution: Keep the main program short. Let it include the required modules, initialize the application, call the main entry point, and then shut down cleanly.
Efficiency: Developers can find code faster because the main program describes the application structure without hiding details inside a giant source file.
Takeaway: The main program should explain the shape of the application, not contain the whole application.
The %include directive lets one source file include
another source file. This allows a large application to be written
as a collection of smaller, focused files while still compiling as
one application.
A good include file has a clear purpose. For example:
invoice_rules.spinc handles invoice business rules.format_utils.spinc handles reusable formatting.invoice_report.spinc handles invoice report output.
Avoid creating include files that are vague catch-all containers.
A file named misc.spinc will eventually become a junk
drawer. A large application needs names that tell the next developer
where to look.
Include order should be deliberate. A simple pattern is:
The goal is to make the dependency direction obvious. Business rules may use data access routines. Reports may use business rule routines. Utility routines should be general and should not depend on application-specific screens or reports.
A large Sheerpower application is easiest to understand when it is organized in layers.
Layers do not need to be complicated. They are simply a discipline: code that talks to the user should not also contain low-level table lookup details. Code that calculates invoice totals should not also format the report header.
Data access modules should hide the details of finding, reading, validating, and saving records. The rest of the application should ask for what it needs through clearly named routines.
With this pattern, the rest of the application does not need to know how the customer table is searched. It only needs to call the routine that answers the question.
Business rules should describe what the application means, not how the screen works or how the report is printed.
This keeps the meaning of the application in one place. If the rule for posting an invoice changes, the change belongs in the business rule module, not scattered across menus, reports, and data entry screens.
Problem: Business rules often get copied into multiple places: once in the screen, once in the report, once in the import program, and once in the batch job.
Solution: Put the rule in one routine with a clear name. Then call that routine wherever the rule is needed.
Efficiency: When the rule changes, one routine changes. The rest of the application automatically uses the corrected logic.
Takeaway: A business rule should have one home.
User interface modules should handle interaction. They should ask questions, display menus, collect input, and call business routines. They should not contain the deep business rules themselves.
The menu routine controls the conversation with the user. It does not calculate invoice totals, validate posting rules, or know the physical structure of the customer table.
Report modules should focus on report output. They may call data access routines and business rule routines, but the report itself should not become the only place where important calculations exist.
This keeps the report readable. The report describes what is being printed. The calculation remains in the business rule module.
Utility modules should contain small, reusable helpers that are not tied to one screen, report, or business object.
Utility routines should stay general. A routine named
calc_money can be used anywhere. A routine named
format_invoice_customer_warning probably belongs in an
invoice module, not in a general utility file.
A large application should expose a small number of clear routines and hide the helper routines that are only used internally.
Think of each module as having two parts:
The important routine is calculate_invoice_total. Other
modules should call that routine. The subtotal and tax helpers are
implementation details.
Problem: When every routine is visible and callable from everywhere, the application slowly loses structure. Other modules begin depending on internal helper routines that were never meant to be public.
Solution: Keep helper routines private when they are only needed inside one module. Expose only the routines that represent the module's intended interface.
Efficiency: Private helpers reduce accidental coupling. The module can be improved internally without breaking the rest of the application.
Takeaway: Public routines are promises. Private routines are implementation details.
Some helper logic is so specific that it should not be visible even as a normal module helper. In those cases, a local routine can keep the helper close to the routine that uses it.
A local routine is useful when the helper has no meaning outside the enclosing routine. This keeps the module cleaner and prevents the helper name from becoming part of the larger application vocabulary.
Scoped routines help a large program keep names under control. In a large application, routine names can multiply quickly. Scoping helps prevent unrelated parts of the program from accidentally depending on each other.
The architectural idea is simple:
This creates a natural ladder of visibility:
As a rule, choose the smallest visibility that works.
Names matter more as programs get larger. A 200-line program can survive vague names. A 50,000-line application cannot.
Good routine names usually say what question they answer or what action they perform.
Avoid names that only describe implementation details or are too general to be useful.
In a large application, the name of a routine is documentation. A clear name reduces the need to open the routine just to understand why it is being called.
Large applications often mix two different kinds of code:
For example, "an invoice cannot be posted until it has at least one detail line" is policy. "read the invoice detail table and count the lines" is mechanics.
Keep policy visible in business rule routines. Hide mechanics in data access helpers.
This routine reads like the business rule itself. The details of how each question is answered are hidden behind clearly named routines.
The following skeleton shows how a larger application can be divided. The exact number of lines is not important. The important point is that each file has a clear purpose.
This is still one application. It is simply divided into pieces that match how the application is understood.
A practical way to organize a large application is to list the major things the user needs to do.
Then map each use case to the modules it needs.
This makes the application easier to test. When the posting logic is wrong, you know to start with the invoice business rules, not with the report or menu code.
A routine interface is the list of information the caller must provide and the value or values the routine returns. In a large application, small interfaces are easier to use correctly.
Compare that to a routine that requires the caller to supply every detail needed for the calculation. That kind of routine is harder to call and easier to call incorrectly.
Problem: Large parameter lists make routines fragile. The caller must understand too many internal details.
Solution: Pass the smallest meaningful input, such as an ID, key, or business object reference. Let the routine gather the details it owns.
Efficiency: Smaller interfaces reduce mistakes and make the application easier to change later.
Takeaway: A good routine hides complexity behind a simple, meaningful interface.
A circular dependency happens when two modules depend on each other. For example, invoice rules call report routines, and report routines call invoice rules. This makes the application harder to understand because neither module is clearly above the other.
A better direction is:
The lower layer should not call back into the higher layer. Data access should not print reports. Business rules should not display menus. Reports should not own core business calculations.
In a large application, comments should explain purpose and boundaries, not repeat every statement.
A short header like this helps future developers understand what belongs in the file and what does not.
A consistent module header makes large applications easier to browse.
This header is not for the compiler. It is for the next human, and for the AI assistant that may help maintain the program later.
A well-structured Sheerpower application is easier for AI tools to work with. The AI does not need to understand the entire program at once. It can work inside the right module and make a small change with less risk.
For example, if the request is:
The correct place is probably:
It is probably not:
Architecture reduces guessing. That helps humans, and it helps AI.
Problem: AI tools can make poor changes when they cannot tell where a feature belongs.
Solution: Organize the application so each file has a clear responsibility. Use names, module headers, and routine boundaries that reveal intent.
Efficiency: The AI can make smaller, safer edits. Fast Sheerpower compiles then allow the change to be checked quickly.
Takeaway: Good architecture turns AI assistance from guessing into guided maintenance.
Before an application grows too large, ask these questions:
Large Sheerpower applications should be organized around clear
responsibilities. Use %include to assemble the
application from focused source files. Keep the main program small.
Separate data access, business rules, reports, user interface code,
and utilities.
Use routine boundaries to make the application easier to understand. Keep public routines clear and stable. Keep helper routines private when possible. Use local routines when the helper belongs to only one enclosing routine.
The result is an application that can grow beyond 50,000 lines while still remaining readable, maintainable, and safe to change.
%include files to keep the application modular
while still compiling it as one application.
|
Hide Description
|
|
|
Enter or modify the code below, and then click on RUN |
|
Looking for the full power of Sheerpower?
Check out the Sheerpower website. Free to download. Free to use. |