The basic unit of programming is the function. You build your program one function (or method) at a time. The smallest thing you can test in a unit test is a function. A function is also the smallest piece of code you can name and hence create a new abstraction. The whole point of a function is to encapsulate some piece of code and make it available to the rest of your program or other programs in a library.
What is a Function Signature?
A function signature includes the function name, its arguments, and in some languages, the return type. There are several other elements that may be present such as exception specification and various qualifiers (especially for class methods). Not all programming languages support all elements.
Python always returns an object from every function, so the return type is not specified in the signature:
def add(a, b):
C specifies the return type and the type of each argument:
int add(int a, int b);
Java has no standalone functions, but its methods can specify what exceptions might be thrown:
public class A
public int add(int a, int b) throws Exception
C# may include generic types in the signature:
public class A
public T add<T> (T number1, T number2)
Function Signature vs. a Contract
A function signature can tell you a lot about the behavior of the function, but never everything. For example, you may figure that an add() function that takes two integers and returns another integer adds its two parameters and returns the sum. You may even be correct, but you still don't know everything. How much memory does the function use? How fast is it? What other resources is it using? What happens if you pass the wrong type of argument? Is there a valid range to the arguments?
For example, for dynamically typed languages, such as Python that don't specify the type of argument or return value you may pass any representation of numbers: int, float decimal, string. In case of an integer overflow, Python may let the exception propagate or handle it internally and convert the result to a float or Decimal. In all languages the function may use a memory cache to look up the result of previous calls or log every call to a database or a remote log service. It may even invoke some Web service to do the work.
To really understand what a function does requires a contract. A contract specifies exactly all these details. A complete contract can't be specified in any programming languages, although some languages go further than others. Eiffel has coined the term "design by contract" and has built-in support for the notion of pre-conditions, invariants and post-conditions, but full-fledged contacts are much more than that. Documentation may be fine for humans, but can't be verified automatically in general.
Pure functions are functions that don't allocate or use any resources and given the same arguments will always return the same result. If you use primarily pure functions then you can eliminate whole categories of behavior, but it's still not a panacea in the general case.
How to Design Your Function Signature
Your function signature is the main portal of your function to the rest of the program. Many potential callers care mostly about the arguments they pass in because they may continue to use them when the function returns. For example, if a function modifies an argument the caller needs to know that. Consider the following signature in C:
void mystery(int * x);
The "mystery" function may modify its "x" argument. To avoid surprises, try to restrict the ability of your function to do "damage". In languages that support it, use immutable specifiers like "const." Avoid pointers and non-const references when possible. If you accept multiple arguments with certain dependencies, consider creating a dedicated object that can verify itself (the caller can't pass invalid combination of arguments).
Designing and building software is hard. The function signature is often considered as a description of what a function does, but that far from being accurate, even if you throw in contracts and documentation. But, all is not lost. By paying attention to your function signature, utilizing language features where possible and using immutable data structures and pure functions you can get pretty far. Well-designed functions are also easier to test, which provide the last piece in the puzzle of building software that works as intended.