Function return MIX type or STRICT type?

In my first article I have mentioned "Method should return a particular type of value and not mix type.", you people might have noticed many programming communities advocating mix return type. So I thought to write a post explaining why I advocate strict return type instead of mix.

I have used two languages to explain some situations, first is PHP(which allows no strict type) and java(strict type). Let's go through below code.

<?php class MyClass
{
    private $firstAttribute;
    private $secondAttribute;

    public function setFirstAttribute($firstAttribute)
    {
        $this->firstAttribute = $firstAttribute;
    }
    public function getFirstAttribute()
    {
        return	$this->firstAttribute;
    }
    public function setSecondAttribute($secondAttribute)
    {
        $this->secondAttribute = $secondAttribute;
    }
    public function getSecondAttribute()
    {
        return	$this->secondAttribute;
    }
    public function doSomeThing()
    {
        echo "Now working fine.";
    }
}

class AnotherClass
{
    public function someFunction($someInputs)
    {
        If (!empty($someInputs)){
            $myClass = new MyClass();
            $myClass->setFirstAttribute($someInputs[‘firstInput’]);
            $myClass->setSecondAttribute($someInputs[‘secondInput’]);
            $myClass->save();
            return $myClass;
        } else {
            return null; //instead return a blank object
        }
    }
}

Class YetAnotherClass
{
    Public function someFunction($someInputs)
    {
        $myClass = (new AnotherClass())->someFunction($someInputs); // Either will return MyClass instance or null
        $myClass->doSomeThing(); #If previous class function will return null, it will through the error, function called with null
    }
}

$yetAnotherClass = new YetAnotherClass();
$someInputs = [];
$yetAnotherClass->someFunction($someInputs);
?>

The above code will throw Call to a member function doSomeThing() on null. Consider another method expecting an instance of MyClass as input and we are sending null, it will throw something like Expected instance of MyClass passed null.

Instead I would prefer

<?php
class AnotherClass
{
    public function someFunction($someInputs)
    {
        $myClass = new MyClass();
        If (!empty($someInputs)){
            $myClass->setFirstAttribute($someInputs[‘firstInput’]);
            $myClass->setSecondAttribute($someInputs[‘secondInput’]);
            $myClass->save();
        }
        return $myClass;
    }
}
?>

This will allow the function to be called as object is present, if we are using property of MyClass there, the property value will be null, but not the object itself, I think I will prefer this instead of making the object null. Another alternative will be throwing an exception and handling it, based on how meaningful I want it to be and its importance.

Now consider a binary search program in java

public class Search {

	public static void main(String[] args) {
		int arr[] = {1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9};
		int key = 5;
		
		System.out.print(binarySearch(arr, key));

	}

	private static int binarySearch(int[] arr, int key) {
		int length = arr.length;
		int start = 0;
		int end = length - 1;
		
		if ( start > end ) {
			return -1;
		} else {
			while ( start < end ) {
				int mid = ( start + end ) / 2;
				
				if ( arr[mid] >= key ) {
					end = mid;
				} else {
					start = mid + 1;
				}
			}
			
			if ( start < length && arr[start] == key ) {
				return start;
			} else {
				return -1;
			}
		}
		
	}
}

Now, this binary search function will return index of the element if it is inside the array or it will return -1. Now think about below situation

public static void main(String[] args) {
	int arr[] = {1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9};
	int key = 1;
	int searched = binarySearch(arr, key);
	if (searched){ //This will not allow you, as if will expect a boolean expression
        System.out.println("The searched element is present in the given array.");
    } else {
        System.out.println("The searched element is not present in the given array.");
    }
}

The binary search will return 0 as index of 1 is 0, here 0 is not treated as boolean false, so the if condition will force you to do something like "searched >= 0", but in PHP it will be treated as false and your function will return "The searched element is not present in the given array." which is wrong. see below code.

<?php

function binarySearch($arr, $key) {
    $length = count($arr);
    $start = 0;
    $end = $length - 1;
    if ( $start > $end ) {
        return -1;
    } else {
        while ( $start < $end ) {
            $mid = ( $start + $end ) / 2;
            if ( $arr[$mid] >= $key ) {
                $end = $mid;
            } else {
                $start = $mid + 1;
            }
        }
        if ( $start < $length && $arr[$start] == $key ) {
            return $start;
        } else {
            return -1;
        }
    }
}
$searched = binarySearch([1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9], 1);
if ($searched){
    echo "The searched element is present in the given array.";
} else {
    echo "The searched element is not present in the given array."; //It will be executed.
}
?>

You need to be smart here, because the language will not give you error, you need to understand what type of value your function is returning or you need to return boolean false when the element is not in the given array, which makes people feel it is better. From similar situation the support for mix type might have come, but once you are returning mix type, that may result into the problem I have shown in first example. It is very important to be aware of return type of your methods as your program depends on the behaviour of the data you are playing with.

Let's consider another situation: We will check if cyclic and if yes find the begin of loop.

ListNode class

public class ListNode {
	protected int data;
	protected ListNode next;
	
	protected ListNode(int data) {
		this.data = data;
	}
	
	private void setData(int data) {
		this.data = data;
	}
	
	public int getData() {
		return data;
	}
	
	public void setNext(ListNode next) {
		this.next = next;
	}
	
	public ListNode getNext() {
		return this.next;
	}
}


LinkedList class

public class ListDemo {
	
	private int length = 0;
	ListNode head = null;

	// Default constructor
	public ListDemo() {
		this.length = 0;
	}

	// Return the first node/head in the list
	public synchronized ListNode getHead() {
		return this.head;
	}
	
	//Return length if the linkedList
	public int getLength() {
		return this.length;
	}
	
	public void insert(int data, int position) {
		// fix the position
		if (position < 0) {
			position = 0;
		}

		if (position > this.length) {
			position = this.length;
		}

		// If the list is empty, make it the only node
		if (this.head == null) {
			this.head = new ListNode(data);
		}

		// If add the front of the list
		else if (position == 0) {
			ListNode temp = new ListNode(data);
			temp.next = head;
			this.head = temp;
		}
		// else find the correct position and insert
		else {
			ListNode temp = this.head;
			ListNode newNode = new ListNode(data);

			while (temp.next != null) {
				temp = temp.next;
			}
			newNode.next = temp.next;
			temp.next = newNode;
		}
		// The list is now one node longer
		this.length++;
	}
	
	public ListNode getLastNode() {
		// Brute-force
		ListNode current = this.head;
		while (current.next != null) {
			current = current.next;
		}
		return current;

	}

	public ListNode getNthNode(int i) {
		ListNode current = this.head;
		for (int j = 0; (j != i && current.next != null); j++) {
			current = current.next;
		}
		return current;
	}
	
	public ListNode findBeginofLoop() {
		
		ListNode fast = this.head;
		ListNode slow = this.head;
		boolean loopExists = false;

		while (fast != null && fast.next != null) {
			fast = fast.next.next;
			slow = slow.next;

			// if fast and slow pointers are meeting set loop exists
			if (fast == slow) {
				loopExists = true;
				break;
			}
		}
		
		if (loopExists) {
			//find begin and return
			while (fast != slow) {
				slow = this.head;
				fast = fast.getNext();
				slow = slow.getNext();
			}
			return fast;
		} else {
			return null;
		}

	}

	// Return a string representation of this collection, in the form ["str1",
	// "str2", . ...]
	public String toString() {
		String result = "|";
		if (this.head == null) {
			return result + "|";
		}
		result = result + this.head.data;
		ListNode temp = this.head.next;
		while (temp != null) {
			result = result + " " + temp.data;
			temp = temp.next;
		}
		return result + "|";
	}

}


LinkedListTest class

public class LinkedListTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		ListDemo listDemo = new ListDemo();
		listDemo.insert(10001, 0);
		listDemo.insert(10002, 1);
		listDemo.insert(10003, 2);
		listDemo.insert(10004, 3);
		listDemo.insert(10005, 4);
		listDemo.insert(10006, 5);
		
//		make the list cyclic
		listDemo.getLastNode().setNext(listDemo.getNthNode(1).getNext());

//      find the begin of the loop
		System.out.println("Data at begin of loop is: " + listDemo.findBeginofLoop().getData());

	}
}

Note: Checking loop exists or not and finding begin of loop are two different responsibility and should be divided into two different situation to maintain single responsibility of method, but to create this example, I have made it one.

Here if the list is cyclic, we are finding the begin of loop and returning the node, but what about the situation, where list do not has cycle? If we will return empty node, it will be not meaningful, if we are returning null, it will generate NullPointerException in LinkedListTest class, so how to handle this situation?

Now check the below code

ListDemo class

public class ListDemo {
	// ....
	public ListNode findBeginofLoop() throws NotCyclicException {
		
		ListNode fast = this.head;
		ListNode slow = this.head;
		boolean loopExists = false;

		while (fast != null && fast.next != null) {
			fast = fast.next.next;
			slow = slow.next;

			// if fast and slow pointers are meeting set loop exists
			if (fast == slow) {
				loopExists = true;
				break;
			}
		}

		try {
			//find begin and return
			while (fast != slow) {
				slow = this.head;
				fast = fast.getNext();
				slow = slow.getNext();
			}
			return fast;
		} catch(NullPointerException $e) {
			throw new NotCyclicException("List is not cyclic.");
		}
	}
}

NotCyclicException class

public class NotCyclicException extends Exception {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * Exception message 
	 */
	private String message;
	
	/**
	 * Constructor
	 */
	public NotCyclicException(String message) {
		this.setMessage(message);
	}
	/**
	 * @return the message
	 */
	public String getMessage() {
		return message;
	}
	/**
	 * @param message the message to set
	 */
	public void setMessage(String message) {
		this.message = message;
	}
}

LinkedListTest class

public class LinkedListTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		ListDemo listDemo = new ListDemo();
		listDemo.insert(10001, 0);
		listDemo.insert(10002, 1);
		listDemo.insert(10003, 2);
		listDemo.insert(10004, 3);
		listDemo.insert(10005, 4);
		listDemo.insert(10006, 5);
		
//		make the list cyclic
		listDemo.getLastNode().setNext(listDemo.getNthNode(1).getNext());

//		find the begin of loop
		try {
			System.out.println("Data at begin of loop is: " + listDemo.findBeginofLoop().getData());
		} catch(NotCyclicException e) {
			System.out.println(e.getMessage());
		}
		
	}
}

If we are throwing an exception and handling it, it will be meaningful as well as the return type is maintained.

A brief story to explain the stuffs little better:-

try-catch seems to be perfect, why sending empty object? Let's look into a situation, I have a system, where lawyers/legal experts and clients registers, you take commission for every transaction, the services of lawyers are divided in many small services and client can choose any combinations while buying the service or booking some time for discussion with the lawyer/legal expert and both of them gets some points for few of the items.

The point calculation is a third party service, when ever a service/booking is added if it is eligible for points, points will be calculated and displayed in page, now because of some reason the service is temporarily shut down and expected to return in some time, you would not like to display the error message "Point earning service is not available temporarily", you want to display the message on profile page and payment page, how you will handle it, one solution will be just making the point object empty or points to be 0 (based on what other point related informations you are getting from the 3rd party). I hope now the empty object makes sense. :) but still checking the response code and resending the request in some interval of time will be a proper solution(may be using a messaging queue). If we do not have that system and we are not ready for situation, then for emergency it can be a solution. If data is not crucial or messaging queue is not affordable you should be fine with sending empty object.

Either your are more disciplined while writing the code or you have to be more discipline while implementing the code.

Note:- You may like to read more about discussions on return types, there are a lot of discussion regarding return type on satckoveflow too and also you may like to read about writing NULL safe code.

By the way recently PHP has a proposal for null safe operator.

$country = $session?->user?->getAddress()?->country;
// do something with $country

and another for null safe call

function f($o) {
  return $o?->mayFail1()?->mayFail2()?->mayFail3()?->mayFail4();
}


I will write more related to the methods in future, keep reading. :)

Soniya AT

Automation & Manual Test Engineer | Selenium, Java, Cucumber, BDD Expert | Ensuring Quality & Efficiency in Testing

4 å¹´

Thanks for sharing knowledge.

Jnanaranjan Sahu

Freelance PHP Developer & Consultant

4 å¹´

A very basic question. How important is this factor, when the programming languages are getting more powerful now a days?

要查看或添加评论,请登录

Prafulla Kumar Sahu的更多文章

  • What is the next step after CRUD application?

    What is the next step after CRUD application?

    If you have just learned to create CRUD application and thinking what is next, this article is for you. This will lead…

  • Creating better methods

    Creating better methods

    A good method should give you 3 important information. Responsibility of method - Method should always have single and…

    2 条评论

社区洞察

其他会员也浏览了