DEV Community

Cover image for Do You Like Pasta? Your Java Code Might!
Cassio Menezes
Cassio Menezes

Posted on

Do You Like Pasta? Your Java Code Might!

What if I told you that a fully functional piece of code would be the worst thing that could happen to your project? Consider a single Java class that spans 1276 lines and manages everything from database transactions to user prompts. It would be twisted up like spaghetti. It functions correctly, but when you need to add a feature, fix a bug, or bring on a new developer, things go bananas. This is the real code from a production system, a class named BtnGeraSerrada.java; it is not an invented horror story. I'm not here to judge; the developer who wrote it did the best they could with the time and resources they had. Instead, let's disassemble this beast to reveal the obscure dangers associated with spaghetti code, as this functional mess now could turn into a maintenance headache tomorrow.

Spaghetti code enters projects under the false pretense of "getting the job done" rather than making an announcement with flashing red warnings. A textbook example is the BtnGeraSerrada.java class I'm presenting, a massive, 1276-line monolith that controls an industrial process to generate sawing operations. It has all the functionality needed to manage transactions, create database entries, validate inputs, and even prompt users in a single location. Though it does not crash, its quality, maintainability, and scalability are all at risk. Let's investigate some of its visible code smells, which are based on fundamental software engineering concepts, and give a sneak peek at improved procedures that we'll discuss in later posts.

You can find the source code on my Gist https://gist.github.com/cassioborgesmenezes/e938db268a431b9928f9fd3308fdc802

Code Smell #1: The God Class

Robert C. Martin highlights the Single Responsibility Principle (SRP) in Clean Code, which states that a class should only have just one reason to change (Martin, 2008). However, BtnGeraSerrada is a "God Class," capable of handling anything from business logic (criaProducao) and persistence (NotaHelper.confirmarNotas) to user interface interactions (contexto.confirmarSimNao). With over a dozen methods, several of which include more than 100 lines, it is a master of none and a jack-of-all-trades. If you have to make changes to this giant when a database schema or a new user interface requirement arrives, this could have unexpected consequences. In contrast, Martin Fowler suggests in Refactoring that one should break huge classes into simpler, focused units (Fowler, 2018). Consider dividing this into three distinct components, each with a distinct function: SawingOrchestrator, DataPersister, and UserPromptService.

Code Smell #2: Long Methods and Poor Readability

Observe the go method, which is a complex web of nested transactions, loops, and conditionals that spans more than 100 lines. Joshua Bloch promotes simplicity and clarity in Effective Java (Bloch, 2017). Long methods like this are harder to read, debug, or test, particularly if variables like movimentosReference change across scopes. According to the Gang of Four in Design Patterns, a more refined approach would use the Command or Strategy pattern to divide tasks like note confirmation or header construction into distinct, reusable units (Gamma et al., 1994). Shorter methods with descriptive names, such confirmSawingNotes, would tell a story rather than hide it.

    public BtnGeraSerrada(final ContextoAcao contexto) throws Exception{
        go(contexto);
    }

    public BtnGeraSerrada() throws Exception{
    }

    private void go(final ContextoAcao contexto) throws Exception {

        int qtdLinhas = contexto.getLinhas().length;

        if (qtdLinhas > 1) {
            contexto.mostraErro("Selecione apenas uma OP");
            return;
        }

        final Registro[] linhas = contexto.getLinhas();
        final BigDecimal id = (BigDecimal) linhas[0].getCampo("ID");
        String status = (String) linhas[0].getCampo("STATUS");

        if (status == null) {
                status = "0";
        }
        if (status.equals("0")) {
            contexto.mostraErro("Ainda nao foram geradas as chapas!");
            return;
        }
        if (status.equals("3") || status.equals("2")) {
            contexto.mostraErro("Essa Serrada ja foi finalizada!");
            return;
        }

        boolean Confirma = contexto.confirmarSimNao("Gerar Serrada", "Confirma serragem do bloco?", 1);

        if (!Confirma){
            return;
        }

        final ConfirmaNotaTxManual confirma = new ConfirmaNotaTxManual();
         JapeSession.putProperty("br.com.sankhya.ctclasse.processando", Boolean.TRUE);
         final FinalWrapper<ArrayList<BigDecimal>> movimentosReference = new FinalWrapper<ArrayList<BigDecimal>>();

        /* Cria Cabeçalho da requisicao do bloco*/             
                SessionHandle hnd = null;
                hnd = JapeSession.open();   
                hnd.setCanTimeout(false);
                System.out.println("TESTE 85");
                hnd.execWithTX(new JapeSession.TXBlock() {
                    @Override
                    public void doWithTx() throws Exception {

                            ArrayList<BigDecimal> m = new ArrayList<BigDecimal>();
                            DynamicVO opVO = (DynamicVO) EntityFacadeFactory.getDWFFacade().findEntityByPrimaryKeyAsVO("SERCAB2", new Object[] { id });

                            m.add(criaReqbloco(contexto));
                            m.add(criaProducao(contexto));
                            if("S".equals(opVO.asString("TERCEIRO")))
                            m.add(criaProducao(contexto, Boolean.TRUE));
                            linhas[0].setCampo("OP", m.get(1));
                            linhas[0].save();

                            System.out.println("TESTE 100");
                            movimentosReference.setWrapperReference(m); 
                    }
                });

                System.out.println("TESTE 105");
                ArrayList<BigDecimal> movimentos = movimentosReference.getWrapperReference();
                JapeSession.close(hnd);
                System.out.println("TESTE 108");
                NotaHelper.confirmarNotas(movimentos);

                for(BigDecimal movimento : movimentos) {
                    if (!movimento.equals(BigDecimal.ZERO)) {
                            confirma.confirmarNota(movimento, true);
                            bloquearNota(movimento);
                        }
                }

                 final FinalWrapper<ArrayList<BigDecimal>> movimentoBaixa = new FinalWrapper<ArrayList<BigDecimal>>();

                    /* Cria Cabeçalho da requisicao do bloco*/             


                            hnd = JapeSession.open();

                            hnd.execWithTX(new JapeSession.TXBlock() {
                                @Override
                                public void doWithTx() throws Exception {

                                        ArrayList<BigDecimal> m = new ArrayList<BigDecimal>();

                                        //m.add(criaReqbloco(contexto));
                                        m.add(criaReqBaixaInsumos(contexto, id));
                                        insereCusto(contexto, id);
                                        //criaReqEntera(contexto);
                                        /*
                                            m.add(criaReqBaixaInsumos(contexto, id));
                                            m.add(criaProducao(contexto));
                                            linhas[0].setCampo("OP", m.get(3));
                                            linhas[0].save();
                                        */
                                        movimentoBaixa.setWrapperReference( m   );

                                }
                            });
                            JapeSession.close(hnd);

                            ArrayList<BigDecimal> movimentosBaixa = movimentosReference.getWrapperReference();
                            NotaHelper.confirmarNotas(movimentosBaixa);
                            for(BigDecimal movimento : movimentosBaixa) {
                                if (!movimento.equals(BigDecimal.ZERO)) {
                                        bloquearNota(movimento);
                                    }
                            }


        atualizaStatus(contexto);

        GeraNFT(contexto);

        JapeSession.putProperty("br.com.sankhya.ctclasse.processando", Boolean.FALSE);

        contexto.setMensagemRetorno("Fim da operacao!!");


    }
Enter fullscreen mode Exit fullscreen mode

Code Smell #3: Tight Coupling and Transaction Chaos

The class has a problem of tight coupling; it is tied to particular implementations by direct calls to EntityFacadeFactory, JapeSession, and utility classes like NotaHelper. You're rewriting half the class if the database layer changes. To make matters worse, there are tons of JapeSession.open() and execWithTXsections dispersed about, increasing the risk of partial commits or leaks. As a way to decouple the process logic from the infrastructure, Fowler's Refactoring recommends removing dependencies and putting them into interfaces (such as a TransactionManager) (Fowler, 2018). This diminishes fragility and complies with SOLID's Dependency Inversion Principle.

Code Smell #4: Error Handling as an Afterthought

BtnGeraSerradahandles errors inconsistently; some methods throw raw Exceptions, others use contexto.mostraErro(), and many don't have the right recovery logic or logging. Martin's Clean Code stresses the need for meaningful and purposeful exceptions (Martin, 2008). Instead of merely leaving users with unclear popup warnings, a centralized error-handling approach - perhaps a specific SawingExceptionwith logging - would make failures traceable and recoverable.

A Real-World Warning

This code is being used in a live system, so it is not broken. However, there are real costs that come with its complexity: adding functionality feels like defusing a bomb, problems hide in its deepest parts, and onboarding new devs takes weeks. The lesson? Functionality alone isnt enough. As Bloch points out, “Good code is not just about working today — it’s about thriving tomorrow” (Bloch, 2017). We'll refactor this monster in upcoming articles, using principles and patterns to create a sustainable masterpiece. Let that be a lesson in what not to do for the time being.

Takeaways

Beware the God Class: A class that reacts inappropriately to SRP and encourages fragility should divide roles to promote resilience and clarity.

Keep It Short and Sweet: Use concise, easily understandable language; lengthy approaches obfuscate intent.

Decouple to Survive: Tight coupling to dependencies restricts your flexibility; instead, employ interfaces and inversion of control.

Handle Errors with Care: Blind spots are caused by inconsistent exception handling; consolidate and appropriately log them.

Quality Is Worthier Than Speed: Invest in structure up front as functional spaghetti code now will cost you badly tomorrow.

Spaghetti code is a debt that accumulates interest, yet it could help you accomplish a deadline. In your projects, have you had to deal with a confusing web like BtnGeraSerrada? Which poor habits have you observed transforming functional code into headaches for maintenance? Tell me about your experiences solving (or suffering) code quality challenges in the comments below!

Full disclosure: this article was written with the aid of Grok, an AI built by xAI, who helped me organize my ideas and polish the content. The concepts and insights, however, are based on my own daily experiences as a coder.

Top comments (0)