Skip to content

Instantly share code, notes, and snippets.

@Ingvord
Last active September 16, 2017 10:09
Show Gist options
  • Save Ingvord/349f0f7033c4a2a7244e0b2b06fa2c91 to your computer and use it in GitHub Desktop.
Save Ingvord/349f0f7033c4a2a7244e0b2b06fa2c91 to your computer and use it in GitHub Desktop.
Example of nested for-if blocks refactoring in JTango

JTango is Java implementation of Tango-Controls. Tango-Controls is a SCADA framework widely used at large research facilities across Europe. As it was developed by enthusiast with scientific background rather than software developers it is usually the case to see a code like this:

  public String[] getPollStatus(final String deviceName) throws DevFailed {
      xlogger.entry();
      final String fullDeviceName = TangoUtil.getfullNameForDevice(deviceName);
      final List<String> pollStatus = new ArrayList<String>();
      boolean deviceFound = true;
      for (final DeviceClassBuilder deviceClass : classList) {
          int nbDevices = 0;
          for (final DeviceImpl device : deviceClass.getDeviceImplList()) {
              if (fullDeviceName.equalsIgnoreCase(device.getName())) {
                  for (final CommandImpl command : device.getCommandList()) {
                      if (command.isPolled()) {
                          final StringBuilder buf = buildPollingStatus(device, command);
                          pollStatus.add(buf.toString());
                      }
                  }
                  for (final AttributeImpl attribute : device.getAttributeList()) {
                      if (attribute.isPolled()) {
                          final StringBuilder buf = buildPollingStatus(device, attribute);
                          pollStatus.add(buf.toString());
                      }
                  }
                  break;
              }
              nbDevices++;
          }
          if (nbDevices == deviceClass.getDeviceImplList().size()) {
              deviceFound = false;
          } else {
              deviceFound = true;
              break;
          }
      }
      if (!deviceFound) {
          DevFailedUtils.throwDevFailed(ExceptionMessages.DEVICE_NOT_FOUND, deviceName + DOES_NOT_EXIST);
      }
      String[] ret;
      if (pollStatus.isEmpty()) {
          ret = new String[0];
          // ret[0] =
          // "Dear Client,\nI am in a great mood today, I ain't gonna bug.\nYour favourite server :)";
      } else {
          ret = pollStatus.toArray(new String[pollStatus.size()]);
      }
      xlogger.exit();
      return ret;
  }

After a very carefull look onto this code one can understand that it does several things simulateneously, namely:

  1. It tries to find a device (aka some entity). For this it iterates over a collection (deviceClass.getDeviceImplList()) calculating a counter (nbDevices) and finally setting a boolean flag (deviceFound)
  2. It builds a poll status for commands and attributes of the found device iterating over its collection of commands and attributes and checking if they are polled
  3. It returns build result or empty array

Having these three points we can now refactor this code into this one:

  public String[] getPollStatus(final String deviceName) throws DevFailed {
    xlogger.entry(deviceName);
    String[] ret = new PollStatusCommand(deviceName, classList).call();
    xlogger.exit(ret);
    return ret;
  }

Basically we delegate execution of the method to a helper object:

public class PollStatusCommand implements Callable<String[]> {...}

Note that this class implements standard Callable interace and can be easily used in asynchronuous calculations. The main entry point of this class is method call:

public String[] call() throws DevFailed {
    final List<String> result = new ArrayList<String>();
 
     final DeviceImpl device = tryFindDeviceByName(deviceName);
 
     result.addAll(getPolledStatuses(device, device.getCommandList()));

     result.addAll(getPolledStatuses(device, device.getAttributeList()));

     //will be String[0] if result is empty
     return result.toArray(new String[result.size()]);
 }

Now it is straightforward to understand what this method is doing:

  1. It tries to find a device by name. Here is the implementation:
private DeviceImpl tryFindDeviceByName(final String deviceName) throws DevFailed {
    //Firstly it concatenates all the possible devices into a single collection using Google Guava 
    // (once we totally switch to Java 8 this code can be replaced with Stream API)
    List<DeviceImpl> allDevices = Lists.newLinkedList(Iterables.concat(Iterables.transform(classList, new Function<DeviceClassBuilder, List<DeviceImpl>>() {
       @Override
            public List<DeviceImpl> apply(DeviceClassBuilder input) {
                return input.getDeviceImplList();
            }
        })));

    // now try to find a device by name
    Optional<DeviceImpl> device = Iterables.tryFind(allDevices, new Predicate<DeviceImpl>() {
            @Override
            public boolean apply(DeviceImpl input) {
                return deviceName.equalsIgnoreCase(input.getName());
            }
        });
        
    //if not found try to find a device by alias using external DataBase    
    if (!device.isPresent()) {
            //try to find device by alias
            device = Iterables.tryFind(allDevices, new Predicate<DeviceImpl>() {
                @Override
                public boolean apply(DeviceImpl input) {
                    try {
                        //returns alias or deviceName
                        return TangoUtil.getfullNameForDevice(deviceName).equalsIgnoreCase(input.getName());
                    } catch (DevFailed devFailed) {
                        logger.warn("Failed to get full name for device {}", deviceName);
                        DevFailedUtils.logDevFailed(devFailed, logger);
                        return false;
                    }
                }
            });
        }
    //if not found throw an exception    
    if (!device.isPresent()) {
            DevFailedUtils.throwDevFailed(ExceptionMessages.DEVICE_NOT_FOUND, deviceName + AdminDevice.DOES_NOT_EXIST);
        }
        return device.get();
    }
  1. build result:
private Collection<String> getPolledStatuses(final DeviceImpl device, List<? extends IPollable> pollableList) {
        Collection<? extends IPollable> polledCommands = Collections2.filter(pollableList, new Predicate<IPollable>() {
            @Override
            public boolean apply(IPollable pollable) {
                return pollable.isPolled();
            }
        });
        return Collections2.transform(polledCommands, new Function<IPollable, String>() {
                    @Override
                    public String apply(IPollable pollable) {
                        return buildPollingStatus(device, pollable);
                    }
                });
}

The logic of each method in this class is quite simple but very powerfull: first aggregate all the required entities and then apply a function to them!

Having this class we can easily unit test its behaviour:

public class PollStatusCommandTest {
    private List<DeviceClassBuilder> classList = new ArrayList<>();

    {
        DeviceClassBuilder mockDeviceClassBuilder = mock(DeviceClassBuilder.class);
        List<DeviceImpl> deviceList = new ArrayList<>();

        createNonPolledDevice(deviceList);

        createPolledDevice(deviceList);

        doReturn(deviceList).when(mockDeviceClassBuilder).getDeviceImplList();

        classList.add(mockDeviceClassBuilder);
    }

    private void createNonPolledDevice(List<DeviceImpl> deviceList) {
        //device without any polled entities
        DeviceImpl mockDeviceImpl = mock(DeviceImpl.class);
        doReturn("1/1/1").when(mockDeviceImpl).getName();
        deviceList.add(mockDeviceImpl);
    }

    private void createPolledDevice(List<DeviceImpl> deviceList) {
        DeviceImpl polledDevice = mock(DeviceImpl.class);
        doReturn("1/1/2").when(polledDevice).getName();

        CommandImpl notPolledCommand = mock(CommandImpl.class);
        doReturn(false).when(notPolledCommand).isPolled();

        CommandImpl polledCommand = mock(CommandImpl.class);
        doReturn(true).when(polledCommand).isPolled();
        doReturn("").when(polledCommand).getLastDevFailed();

        AttributeImpl notPolledAttribute = mock(AttributeImpl.class);
        doReturn(false).when(notPolledAttribute).isPolled();

        AttributeImpl polledAttribute = mock(AttributeImpl.class);
        doReturn(true).when(polledAttribute).isPolled();
        doReturn("").when(polledAttribute).getLastDevFailed();

        doReturn(Lists.newArrayList(polledCommand, notPolledCommand)).when(polledDevice).getCommandList();
        doReturn(Lists.newArrayList(notPolledAttribute, polledAttribute)).when(polledDevice).getAttributeList();
        deviceList.add(polledDevice);
    }

    @Test
    public void noPolledCommandsNorAttributes() throws Exception {
        PollStatusCommand instance = new PollStatusCommand("1/1/1", classList);
        String[] result = instance.call();

        assertArrayEquals(new String[0], result);
    }

    @Test
    public void polledCommandsAndAttributes() throws Exception {
        PollStatusCommand instance = new PollStatusCommand("1/1/2", classList);
        String[] result = instance.call();

        assertEquals(2, result.length);
    }

    @Test
    public void nonExistingDevice() throws DevFailed {
        try {
            new PollStatusCommand("1/1/3", classList).call();
        } catch (DevFailed devFailed) {
            assertEquals("API_DeviceNotFound", devFailed.getMessage());
        }
    }
}

Hope this can be useful!

The same refactoring done in Kotlin:

/**
     * @return an array of strings with polling info
     * *
     * @throws DevFailed in case device cannot be found
     */
    @Throws(DevFailed::class)
    override fun call(): Array<String> {
        val device = tryFindDeviceByName(deviceName)

        with(device) {
            return (addPolledStatus(this, commandList) + addPolledStatus(this, attributeList)).toTypedArray()
        }
    }

    private fun addPolledStatus(device: DeviceImpl, pollableList: List<IPollable>) =
            pollableList.filter { it.isPolled }.map { buildPollingStatus(device, it).toString() }


    @Throws(DevFailed::class)
    private fun tryFindDeviceByName(deviceName: String): DeviceImpl {
        val allDevices = classList.flatMap { it.deviceImplList }

        var device = allDevices.find { deviceName.equals(it.name, ignoreCase = true) }

        return device ?: allDevices.find {
                try {
                    //returns alias or deviceName
                    TangoUtil.getfullNameForDevice(deviceName).equals(it.name, ignoreCase = true)
                } catch (devFailed: DevFailed) {
                    logger.warn("Failed to get full name for device {}", deviceName)
                    DevFailedUtils.logDevFailed(devFailed, logger)
                    false
                }
        } ?: throwDevFailed<DeviceImpl>(ExceptionMessages.DEVICE_NOT_FOUND, deviceName + AdminDevice.DOES_NOT_EXIST)
    }

Credits to Roman from JetBrains who helped me with this at JavaZone 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment