(this answer is largely taken from where I posted the same text as an answer to another question, but it wasn't accepted and so probably doesn't get seen much!)
As you have said in your question, there are a number of functions you can use at runtime to find other objects, but these - as you've suggested - are slightly tedious and don't result in easily reusable code.
The main ways to access other objects and other components are listed on these two manual pages:
Accessing Other Objects
Accessing Other Components
However, there is one method listed within those pages that is quite unique to Unity and very useful, but it is not described in great detail. As a result, many beginners don't spot this feature, so I'm going to write it up in more detail here.
Interestingly - on the flip side - many newcomers to Unity who have experience in other programming environments (which don't have this feature) feel the urge to rebel against it and go for an "All-Code" approach. I'm talking about:
Drag-and-drop Editor References
This is the way that you can create references to scripts on other game objects by literally dragging and dropping GameObjects in the editor. I'm not sure if there's a specific technical term coined for these, but I'm going to call them 'dragged references' from here on.
I'm not suggesting that drag-references are the best option in all situations, I'm mainly stating here that it's good practice to know about this and understand it, and use it where appropriate.
In practice - for any project of significant complexity - you will almost always need a combination of approaches, incuding dragged references, "object managers" which instantiate and track references to dynamically created objects (such as bullets, enemies), direct function calls between objects and event systems.
For instance - even though you could - you probably don't want to creat every instance of each ship in the scene in formations for the levels. Instead - if you have, say, 20 different kinds of ship in the entire game, each defined as a prefab, it might be an idea to have a sort of "Ship Library Manager" class, which itself has a drag-reference just to each ship prefab. This "ship manager" can then instantiate them in whatever formations you like, which can be defined programatically. Because it's creating the ships, it has access to each of their instances which it can store in an array for easy direct messaging.
That said, here is how you go about creating dragged-references. They are created by a simple two-step process:
Step 1:
Create a variable in your script, whose type is the exact name of the type of script you want a reference to. For example: a simple "bat and ball" game. You have a player GameObject, which has a "Player" script attached, and a ball GameObject with a "Ball" script attached. In your "Player" script, you would have:
// in Javascript:
var ball : Ball;
// or in C#
public Ball ball;
This creates a public variable which can contain a reference to an instance of your "Ball" script. (In this example, the variable name is "ball" - a lower-case version of the script's name. This is a common convention, but you could pick any suitable name you like).
Because the variable is public, and your "Ball" script is effectively a Component (because it inherits from MonoBehaviour), the Unity Editor spots this, and makes the variable visible as a special "slot" which is visible in the inspector. You would need to click on your Player GameObject in the hierarchy to see this.
Step 2:
Make sure the target variable slot is visible in the inspector, then click and drag the object which has the desired script to be referenced, from the hierarchy pane into the variable slot. This now makes an automatic reference which you can use at runtime to refer to the instance of that script. For the Player and Ball example, you'd click the "Player" object, so that you can see "Ball" variable slot. Then you'd drag the "Ball" GameObject from the hierarchy pane straight into that variable slot.
That's it.
You can also have a script which has variables that can contain references to other instances of the script itself. For example "ScriptA" might have a public variable which is also of type "ScriptA". A practical example of why you might want this could possibly be a tennis game, where you might have variables set up in the "Player" script like this:
// script is called "Player.js"
var ball : Ball;
var otherPlayer : Player;
In this example, we have one public variable ready to have the 'ball' object dragged into it. However we also have a variable called "otherPlayer", whose type is "Player" (the name of the script itself). This is so that you could have two players, and each player would be set up with a dragged-in reference to the other player - their opponent.
You can also have arrays of dragged references. For example, in a game where there are ten targets to hit, and each target has a "Target.js" script attached, you might have this variable on the "Player" script:
// in Javascript:
var targets : Target[];
// or in C#
public Target[] targets;
This creates an array which can be filled with as many "Target" references as you like. In the inspector, you'd then assign the array a size of "10", and then drag each target gameobject into the 10 slots available in the inspector.
There are limitations to using Dragged References, the major one being that they become impractical when there are many references to be made. (For example, it would be incredibly tedious to drag-assign references from each team member to every other team member in a game of football!). In these cases, it's probably better to either write some script to grab these references at runtime using the methods mentioned in the link at the top of this answer, or write an Editor Script to help you automatically build these references at edit-time.