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. :)
Automation & Manual Test Engineer | Selenium, Java, Cucumber, BDD Expert | Ensuring Quality & Efficiency in Testing
4 å¹´Thanks for sharing knowledge.
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?