-3

Writing code, in my opinion, usually involves 2 kinds of code: logical and functional code.

While the logical part of the code always differs between every app and its goals, often the functional part doesn't change that much (especially the low level simple functional code) and is often reused a lot in the same project.

For example, in my app I use a lot of File related code. So I created a dedicated util class like this:

public final class FileUtils {

/* Constants */
private static int DEFAULT_BUFFER_SIZE = 1024;
public static final String PACKAGE_FILES_DIR = MyApplication.getContext().getFilesDir().getAbsolutePath(); // Usually: /data/data/package name/files
public static final String MAIN_LOCAL_STORAGE = Environment.getExternalStorageDirectory().getAbsolutePath(); // Usually:


/*
 * ================
 * Constructor
 * ================
 */

private FileUtils() {}


/*
 * ================
 * Helper methods
 * ================
 */

/**
 * Write text to file.
 * <a>{@see javadoc at the beginning of this class}</a>
 *
 * @param path     the path
 * @param fileName the file name
 * @param text     the text
 */
public static void writeTextToFile(String path, String fileName, String text) {

    File file = new File(path, fileName.toString());

    try {
        file.createNewFile();
        FileOutputStream fos = new FileOutputStream(file);
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, "UTF-8");
        Writer out = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));
        out.write(text.toString());
        out.close();
        fos.close();
        outputStreamWriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * Check and create sub dierctory.
 * <a>{@see javadoc at the beginning of this class}</a>
 *
 * @param path       the path
 * @param folderName the folder name
 */
public static void checkAndCreateSubDierctory(String path, String folderName) {
    // Check and if needed, create logs subdirectory
    File subDir = new File(path, folderName);
    if (!subDir.exists()) {
        subDir.mkdir();
    }
}

public static void createZip(){
// create a zip including several files
}
}

The thing is, I haven't seen much code written this way. The pro's and con's that I see in this approach are:

Pro's:

  • Makes the logical part of the code much clearer

  • Functional code is not rewritten and is accessible from anywhere

  • To my knowledge, the point of static is code that doesn't need to be instantiated and is common to all.

Con's:

  • It can result in a very large amount of static code.

Another possible approach would be:

public class FileUtils {

/* Constants */
private static int DEFAULT_BUFFER_SIZE = 1024;
public static final String PACKAGE_FILES_DIR = MyApplication.getContext().getFilesDir().getAbsolutePath(); // Usually: /data/data/package name/files
public static final String MAIN_LOCAL_STORAGE = Environment.getExternalStorageDirectory().getAbsolutePath(); // Usually:


/*
 * ================
 * Helper methods
 * ================
 */

/**
 * Write text to file.
 * <a>{@see javadoc at the beginning of this class}</a>
 *
 * @param path     the path
 * @param fileName the file name
 * @param text     the text
 */
public void writeTextToFile(String path, String fileName, String text) {

    File file = new File(path, fileName.toString());

    try {
        file.createNewFile();
        FileOutputStream fos = new FileOutputStream(file);
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, "UTF-8");
        Writer out = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));
        out.write(text.toString());
        out.close();
        fos.close();
        outputStreamWriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * Check and create sub dierctory.
 * <a>{@see javadoc at the beginning of this class}</a>
 *
 * @param path       the path
 * @param folderName the folder name
 */
public void checkAndCreateSubDierctory(String path, String folderName) {
    // Check and if needed, create logs subdirectory
    File subDir = new File(path, folderName);
    if (!subDir.exists()) {
        subDir.mkdir();
    }
}

public void createZip(){}

}

Pro's:

  • Doesn't use static code

Con's:

  • Every time I would like to use the code, I would need to instantiate the class, something that could lead to a creation of a lot of objects

Of course, there is always the option to just write the code locally in every class, but that to my knowledge also violate OOP principles and results in a greater amount of code that could be saved...

I searched about this a lot, and couldn't find concrete answers or approach.
Is this an acceptable approach to Object Oriented Programming?
Can someone explain why and what are alternatives to this approach?

Rhys Johns
  • 426
  • 2
  • 8
  • 3
    Questions containing words like "acceptable" are not answerable unless you specify your criteria for judging what you consider acceptable and not acceptable (hopefully without resorting to tautologies like "best practice"). – Robert Harvey Aug 02 '17 at 20:25
  • Possible duplicate of [What are the valid uses of static classes?](https://softwareengineering.stackexchange.com/questions/266938/what-are-the-valid-uses-of-static-classes) – gnat Aug 02 '17 at 20:29
  • @gnat How is my question duplicate to what you referred to?! I'm asking about implementing reusable code, whether with static code or not, and the thread you referred to refers to all that is relevant to static... – Roy Hen Engel Aug 02 '17 at 20:33
  • 3
    Your question doesn't provide any code examples other than `static` ones. – Robert Harvey Aug 02 '17 at 20:35
  • Please don't close questions right away!!! Static code is only a part of the answer at best...I ask about approaches as per OOP in relating to reusable code... – Roy Hen Engel Aug 02 '17 at 20:35
  • @RobertHarvey If I'm not mistaken, I'm not supposed to provide every example on earth... What I do need to do though is ask a question that can be answered with a yes or no and a proper explanation why. My example may not be the only option, but that is why I asked here... – Roy Hen Engel Aug 02 '17 at 20:38
  • Please allow the question! – Roy Hen Engel Aug 02 '17 at 20:39
  • @RobertHarvey, I rephrased the question...now better? – Roy Hen Engel Aug 02 '17 at 20:48
  • 1
    You haven't explained in your question why you think `static` code is a problem in and of itself. Also, you haven't clarified your use of the word "acceptable." – Robert Harvey Aug 02 '17 at 20:55
  • @RobertHarvey Yes I have... acceptable = is still in line with OOP principles..."acceptable approach to Object oriented programming"... about the static code maybe you are right...but I think that is quite obvious isn't it? There is enough documentation about why using static code is generally not the way to go...So explaining it would be explaining the obvious... – Roy Hen Engel Aug 02 '17 at 21:08
  • OOP principles are just that... *principles.* They don't come with pros and cons unless *you* provide them. And you provided a tautology, like I asked you not to: `acceptable = is still in line with OOP principles` – Robert Harvey Aug 02 '17 at 22:08
  • This is certainly not an endorsement, but the static version of writeTextToFile is built into PHP as http://php.net/manual/en/function.file-put-contents.php – bdsl Aug 02 '17 at 23:07
  • It doesn't have to be instantiated every time, and actually if it was instantiated every time you might as well call the static. You can instantiate the object once and pass a reference to it to functions that need it. – bdsl Aug 02 '17 at 23:10
  • Most OOP "principles" are unfortunately, simply called principles, unrelated to software engineering in general. – Frank Hileman Aug 04 '17 at 23:54

2 Answers2

1

Object-oriented practitioners typically object to your static method approach for a number of reasons:

  1. You don't get OO encapsulation.
  2. You can't use mock objects with a static class.
  3. Static methods can hold static state, which is difficult to test.
  4. Your static method may depend on other static methods that are broken.

Generally these objections arise from people who are accustomed to programming using objects. Before there were objects, there were procedural languages like Fortran, Pascal and C. Objects became commonplace in the programming landscape because they provide a familiar, easy to use container for encapsulating data and related behavior.

But Linus Torvalds is living proof that you can program rather large systems without ever touching an object (he programs in C, which isn't an object-oriented language), using what are essentially structures and methods.

So let's look at the objections in turn:

You don't get OO encapsulation

True. But there are ways around that. In C, you can do it by following simple, sensible rules for naming things and organizing your libraries and header files. Is that a lot of work? It can be. It can certainly be more work than simply using namespaces and classes, but I've also seen the convenience of namespaces and classes abused to create endlessly-bloated architectures.

You can't use mock objects with a static class

But you don't need them, either. A proper static method doesn't require mocks, stubs, interfaces or any of the other machinery typically reserved for OO unit testing. You just hand the method some parameter values and assert the return result. That's it.

Static methods can hold static state, which is difficult to test

Then don't hold static state. You're not going to need it anyway, unless you're writing a logger or creating a poor man's singleton.

Your static method may depend on other static methods that are broken

Presumably, you have test methods that exercise that other, presumably broken method, eh?

The OO developers examined this problem with respect to private methods, and have reached the opposite conclusion: you shouldn't have to test your private methods because they're already being tested indirectly through your public methods!

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • Thank you for the points you pointed out... All of that is understood... So what are the alternatives? Should I use functional methods locally in every class? Doesn't that also violate OOP principles? – Roy Hen Engel Aug 02 '17 at 21:00
  • Um, what prevents you from doing exactly what you're doing now? – Robert Harvey Aug 02 '17 at 22:07
  • 1
    The issue with statics and mocking isn't that you can't use mocks to test a static method - you can, if it takes an object argument. The issue is that if you want to test some **other** method, which depends on the static you can't easily replace it with a mock or similar for the purpose of the test. For simple utility methods that may not matter, but typically a unit test would avoid calling a real writeTextToFile() function. – bdsl Aug 02 '17 at 23:05
  • This contains some generalizations about static methods... I am sure your link about static methods resolves this, but if there is no instance data needed, of course methods should be static. In the original code examples, I saw no need for instance data. – Frank Hileman Aug 03 '17 at 17:31
1

These two things here are not "pros" at all:

  • Functional code is not rewritten and is accessible from anywhere

  • To my knowledge, the point of static is code that doesn't need to be instantiated and is common to all.

"Accessible from anywhere" and "common to all" means you are not managing your dependencies at all. Change something in one place and there's no telling how many parts of your code will be affected. This is the opposite of good design. It's as bad as using global variables everywhere.

Symbols, including variables and function names, should be scoped as tightly as possible, and only made available to those parts of the system that need them. What's more, when you form a dependency, it should be on an interface, not a concrete implementation (see Liskov Substitution Principle). Static methods can't participate in an interface.

John Wu
  • 26,032
  • 10
  • 63
  • 84
  • You should not use interfaces unless needed. Liskov substitution is not about interfaces, it is about code correctness in the face of a sub-component swap. It involves either an interface or a base class equivalent in a typical OO language. – Frank Hileman Aug 03 '17 at 17:33
  • Either way, static methods don't fit. – John Wu Aug 03 '17 at 18:06
  • Static methods don't fit, but there is no instance data, so instance methods are not appropriate either. Substitution of one type for another is useful sometimes, but why is it needed in this case? There was no requirement in the original question. – Frank Hileman Aug 03 '17 at 19:21
  • Substitution of one type for another is essential for unit testing, where a test stub is substituted for a dependency that is being isolated away. Also essential for the `D` in [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)). – John Wu Aug 03 '17 at 19:33
  • You can substitute one type in another in a static method by making the parameters interface types. – Robert Harvey Aug 03 '17 at 19:52
  • @JohnWu Delegates provide even more flexibility than type substitution, should we use those instead? No. Neither delegates nor polymorphism is justified. SOLID primarily adds unnecessary complexity, which I am arguing against. Unit testing can be done without type substitution or delegates. You are suggesting to mock the file system? – Frank Hileman Aug 03 '17 at 22:29
  • [Appeal to extremes is fallacious](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644946(v=vs.85).aspx). And yes you should isolate away the file system if you are unit testing. Otherwise you are actually integration testing. And probably doing it against a file system that does not match the actual system under test. – John Wu Aug 04 '17 at 01:48
  • @JohnWu I suppose appeal to extremes is fallacious, when you never encounter such extremes in work. Unfortunately I do. As such, I will always argue against unnecessary complexity. Mocking the file system is an extreme case, perhaps needed in an embedded system going to Mars. – Frank Hileman Aug 04 '17 at 23:53
  • No, actually it is pretty common [to mock the file system](https://stackoverflow.com/questions/1087351/how-do-you-mock-out-the-file-system-in-c-sharp-for-unit-testing) and even [the system clock](https://stackoverflow.com/questions/2425721/unit-testing-datetime-now). If you didn't, how would you test the "disk full" failure mode-- fill the disk up? How would you test for expired accounts, reset the VM's system clock? I know... kids today and their crazy ideas.... – John Wu Aug 04 '17 at 23:56