Written by

Senior Startups and Community Programs Manager at InterSystems Corporation
Question Evgeny Shvarov · Feb 12

What is the recommended size if the ObjectScript class?

Hi Developers!

Currently I have 2000+ lines in a class, and I think it's a lot already? What are the best practices? 

if 10K lines OK? How do you cope with it? Please share?

Product version: IRIS 2025.3

Comments

Eduard Lebedyuk · Feb 12

I think you might want to track these numbers instead:

  • Number of properties. $lb is list, so the larger the number of elements in a list the longer the time to get to the last element. Also larger larger lists exhaust the global buffer faster, so they might be less optimal. 
  • Method size in sloc. Ideally, your class shouldn't have methods spanning hundreds (thousands) of lines.

I wouldn't worry about total class length, but rather the number of individual elements and method sizes.

There's also class limits docs, but if you hit them, you probably have an issue already.

0
Robert Cemper · Feb 12

My personal approach would be:

  1. separate instance code from Class Methods
    This covers all properties and especially Calculated Properties
    Or simplified, all code tied to data storage
  2.  Class Methods work on referenced objects but have no dedicated data store (i%...)
    Simplified, they are code-only components.
  3.  Class Methods could be bundled in several Classes.
0
Robert Barbiaux · Feb 12

Why is there so many lines in this class ? 

From code quality point of view, a class exceeding 500 lines is usually considered a candidate for refactoring.

Is the code generated ?

0
Evgeny Shvarov  Feb 12 to Robert Barbiaux

Why is there so many lines in this class ? 

A lot of logic.

From code quality point of view, a class exceeding 500 lines is usually considered a candidate for refactoring.

So, 1000+ maybe is already a bit off? Is there any industry standard? Or it's difficult ot cope for a human? )

Is the code generated ?

May I not answer on this question? :)

0
Scott Roth · Feb 12

And I had thought I had set a record with the number of lines I had in a process.... 

0
Scott Weithman · Feb 13

Hi Evgeny!

First, kudos on developing such complex logic; no easy feat! As far as the question itself, it's a good question, but leading down the wrong line of thought.

The question shouldn't be "how big can I make this one class," but rather, "how is this logic and code going to be maintained." Having a class with so many lines of code, while possible, will be a nightmare to extend and maintain.

For example, say you're working on adding functionality and discover a bug in the code already in production. As a monolithic design, are you going to remove your not-ready-for-production feature, fix the bug, promote to live, and add the feature code back? What if you bring on another developer? Depending on server side/client side change control, you may end up limiting to one developer being able to work on the code at any given time. And what about multi threading and bottlenecks identified during testing and live processing? A single class will already likely need refactoring to deal with these sorts of things.

There is a reason ISC is implementing IPM and "micro services" as a strategy moving forward. The less monolithic, the better in the long run (within reason, as you don't want code scattered everywhere for no reason).

Look at options to break things up a bit. You'll thank yourself later!

0
Evgeny Shvarov  Feb 13 to Scott Weithman

Thank you, @Scott Weithman, for sharing the real-life cases! This all makes sense!

It'd be ideal to have guidelines on how to better distribute logic among a set of classes for complex solutions.

And I agree IPM can be super-helful not only for distribution and deployment but also for distributing the shared logic over the large solution.

My particular case is rather simpler: I have a full-stack app, and the /app/api spec file is getting bigger and bigger, and the impl class is already more than 2K lines. So I'm inclining to split it but the issue is that the current design of REST-API we have in  InterSystems will demand me to introduce another web app endpoint, so i will have /app/functionality1/api and /app/functionality2/api and so on. 

0
Scott Weithman  Feb 13 to Evgeny Shvarov

 Ah, I see. I think you could still apply the same logic as follows:

Create the implementation class such that it only has the stub methods and calls into other classes for the processing logic. Like creating a service to receive the messages and pass it on to the process for business logic, so too can you leave your stub classes rather simple and pass to another class (set of classes) for the processing logic.

If you've gotten up in the thousands of lines simply from bare bones stub classes, exposing another endpoint is likely the way to go, where there will be a decent separation point somewhere in your API. Same issues apply as before; if you have so many stub methods that you're hitting this many lines in your class, you've got so many stub methods in a single endpoint that future dev work will be a pain.

From the ISC documentation Intro to Creating REST Services, they have the following example stub class and show placing your processing logic in the stub method as below:

/// A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification<br/>
/// Business logic class defined by RESTSpec in petstore.spec<br/>
Class petstore.impl Extends %REST.Impl [ ProcedureBlock ]
{

/// If ExposeServerExceptions is true, then details of internal errors will be exposed.
Parameter ExposeServerExceptions = 0;

/// Returns all pets from the system that the user has access to<br/>
/// The method arguments hold values for:<br/>
///     tags, tags to filter by<br/>
///     limit, maximum number of results to return<br/>
ClassMethod findPets(tags As %ListOfDataTypes(ELEMENTTYPE="%String"), limit As %Integer) As %Stream.Object
{
    //(Place business logic here)
    //Do ..%SetStatusCode(<HTTP_status_code>)
    //Do ..%SetHeader(<name>,<value>)
    //Quit (Place response here) ; response may be a string, stream or dynamic object
}

...
}

Rather than placing all of the logic here in the implementation class, which works well for APIs with a typical number of resources, you could instead pass off the business logic to other function calls outside of the implementation class. That way you only need to check out and modify the implementation class if you're adding URI updates, and have the processing classes that can be updated without impacting the implementation class (i.e. separate your front end vs back end dev work into separate classes).

It's a bit difficult to standardize how one should create these class separations, as it's highly dependent on user/customer preference, change control methodology, dev teams, and API. If you wanted to share more about the number of resources and stub method count, even in a private message, I'd be happy to try and provide a more specific set of thoughts.

0
Robert Barbiaux · Feb 13

Hi Evgeny,
Thank you for sharing the details, now I see why you have so much lines in a single class 😊
As an example of what Scott and Robert are explaining in their excellent answers, when dealing with a REST API spec defining (mostly) CRUD operation on many resources, I think one way to divide and, hopefully, conquer is to delegate the implementation of operations related to each resource to a separate class.
Taking as example petstore, for which, of course, this is an overkill, this would result in five classes :

  1. Class dc.petstore.impl Extends %REST.Impl : (generated), all endpoints class methods, with most bodies being single line call to another class method
  2. Class dc.petstore.Commons : logic common to all the classes below
  3. Class dc.petstore.PetOperations Extends Commons : class methods dealing with 'pet' resource (CRUD + other)
  4. Class dc.petstore.StoreOperations Extends Commons : same for 'store' resource
  5. Class dc.petstore.UserOperation Extends Commons : same for 'user' resource

dc.petstore.impl class methods simply delegate the call to resource class, e.g.

ClassMethod findPetsByStatus(status As%String) As%DynamicArray
{
    return##class(PetOperations).FindByStatus(status)
}

Hope this helps

0
Evgeny Shvarov  Feb 13 to Robert Barbiaux

Thank you @Robert Barbiaux ! Makes sense! I'll take a look if I can implement it vs what I'm doing.

I was thinking of introducing several IRIS web-apps serving one frontend that addresses different parts of logic as a spec file and implementation far beyond 2K of lines already. But not sure if it is a good/bad practice to have several web apps, and if it is a common approach. Of course, it brings a burden of accesses/roles/security whoops, etc.

0
Sergei Shutov  Feb 14 to Evgeny Shvarov

Hi Evgeny,

In my opinion, the best approach is to divide API endpoints into multiple subclasses using Forward functionality. See for example an implementation of %Api.InteropEditors class in %SYS namespace, it showcases how OpenApi can be implemented with Forward functionality

0
Herman Slagman · Feb 16

More philosophical is the book by Martin Fowler: Refactoring.
Not giving hard figures, but more ways of looking at your code base with common sense.
But mind you, AI coding will change all that, AI agents don't need comprehensible code, in fact, they might just make code more 'efficient' (not a variable 'name', but just 'a') in a way that we humans cannot understand anymore.
Maybe agents devise a 'middle language' that only AI coding agents can understand, highly efficient.
And then methods of thousands of lines of code might not be an exception anymore.

0
Evgeny Shvarov  Feb 16 to Herman Slagman

Right. I was thinking, too, that AI might code directly in obj-code for "efficiency" maybe with unit-tests keeping "taking care" of logic consistancy.

0