Why does Java require the throws clause? Good or bad language design?

J

James Harris

I have a number of books on Java but none seem to answer the
fundamental question on throws clauses: /why/ force the programmer to
declare what a method /may/ throw?

To quote one: "If you write a method that throws an exception, then
the Java compiler wil require that you do one of two things: you must
either declare that the method throws the exception using the 'throws'
keyword, or else you must provide a catch exception handler to catch
that exception." - Java Programming Explorer.

Is there really value in Java's behaviour? Or is this an unnecessary
burden on the programmer? Why not simply accept exceptions can be
thrown which are not listed, and deal with them in the innermost catch
clause which matches?

Conversely, if there is a chain of methods: aa calls bb calls cc calls
dd, and dd declares that it throws FileNotFoundException, if we don't
want to handle it in cc do we need to declare that it also throws
FileNotFoundException... and so on through callers to cc etc?

In terms of code maintenance, if dd could once throw a given exception
but now, because the relevant code has been removed, it cannot does cc
still need to provide for handling the exception simply /because/ it
is listed in dd's throws clause?

Any replies appreciated and please keep comp.lang.misc in the
crosspost list as it's relevant to language design principles. Thanks.
 
A

Alex Hunsley

James said:
I have a number of books on Java but none seem to answer the
fundamental question on throws clauses: /why/ force the programmer to
declare what a method /may/ throw?

To quote one: "If you write a method that throws an exception, then
the Java compiler wil require that you do one of two things: you must
either declare that the method throws the exception using the 'throws'
keyword, or else you must provide a catch exception handler to catch
that exception." - Java Programming Explorer.

The theory is that by mkaing the programmer explicitly acknowledge what
(checked) exceptions may be thrown, they will deal with them appropriately.
Of course, it is possible to write:

catch (Exception e) {
// nothing
}

But then of course the 'blame' lies squarely on the programmer.
Is there really value in Java's behaviour?
Yes.

> Or is this an unnecessary
burden on the programmer?

There is some burden - obviously declaring "throws" on lots of methods
can be annoying - but you can use strategies like wrapped exceptions (of
which I posted in comp.lang.java.programmer in the last few days).
Why not simply accept exceptions can be
thrown which are not listed,

The theory is that the lack of explicitness would a bad thing. And if
you think about it, whether or not a method throws a certain type of
exception *is* in effect part of that method's interface.

and deal with them in the innermost catch
clause which matches?

Doesn't make much sense. Lower down code doesn't always know exactly how
to handle the exceptional circumstance, and so you'd end up signalling
to higher levels that something bad happened - so exceptions handle that
(at some cost, e.g. setup time, but for exceptional circumstances, that
isn't so bad).
Conversely, if there is a chain of methods: aa calls bb calls cc calls
dd, and dd declares that it throws FileNotFoundException, if we don't
want to handle it in cc do we need to declare that it also throws
FileNotFoundException... and so on through callers to cc etc?

Yup. Or use a wrapped exception to wrap all possible exceptions, hence
protecting your interfaces from mucho changes everytime some deep code
throws a new sort of exception (or *stops* throwing some sort).
In terms of code maintenance, if dd could once throw a given exception
but now, because the relevant code has been removed, it cannot does cc
still need to provide for handling the exception simply /because/ it
is listed in dd's throws clause?

Java complains (compile error) if you declare a method as throwing a
checked exception when in fact it doesn't. So your above situation can't
happen - it won't compile.

lex
 
M

Marcin 'Qrczak' Kowalczyk

Dnia 07-02-2007, śro o godzinie 00:11 -0800, James Harris napisał(a):
Is there really value in Java's behaviour?

Some people believe so. Others claim that the cure is worse than the
disease. I think it's not worth the trouble.

http://radio.weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
http://www.mindview.net/Etc/Discussions/CheckedExceptions
http://www.artima.com/intv/handcuffs2.html

See also: http://java.sun.com/javase/6/docs/api/java/lang/ThreadDeath.html
"The class ThreadDeath is specifically a subclass of Error rather than
Exception, even though it is a "normal occurrence", because many
applications catch all occurrences of Exception and then discard the
exception."

It's easy to imagine *why* many applications are tempted to catch all
occurrences of Exception and then discard the exception.
 
L

Lew

Alex said:
The theory is that by mkaing the programmer explicitly acknowledge what
(checked) exceptions may be thrown, they will deal with them appropriately.
Of course, it is possible to write:

catch (Exception e) {
// nothing
}

But then of course the 'blame' lies squarely on the programmer.
The theory is that the lack of explicitness would a bad thing. And if
you think about it, whether or not a method throws a certain type of
exception *is* in effect part of that method's interface.

There are always unchecked exceptions. But Alex is right, in many cases you
need or want to make the exception part of a method's interface, and declaring
it so is no different than declaring that a method, say, takes a String arg.
Does declaring method parameters place an "undue" burden on a programmer?

If you as a programmer really don't see the value of declaring exceptions,
don't throw them, or rely entirely on unchecked exceptions. After a while you
will discover scenarios where dealing with the results thereby is actually
harder than dealing with checked exceptions.

One of the major flaws, perhaps the major flaw with much production code is
that the programmers did not account for all possible exceptional
circumstances. Such programs fubar on their users all the time. You have to
take the care as a programmer to deal with things like unexpected inputs, lost
connections and the myriad of unhappy events that can befall a program.

With or without exceptions.

There is no responsible way to avoid this aspect of programming.

The burden isn't in the declaration of exceptions. Au contraire, exceptions
help you reduce the burden of dealing with all the corner cases.

- Lew
 
S

Simon

Alex said:
Java complains (compile error) if you declare a method as throwing a
checked exception when in fact it doesn't. So your above situation can't
happen - it won't compile.

It will, since overriding methods in subclasses could throw the exception. One
thing that does not compile is catching an exception that is not thrown. The
first method compiles, the second does not:

public class Test {

public void testThrows() throws java.io.FileNotFoundException {
}

public void testCatch() {
try {
System.out.println("Test");
} catch (java.io.FileNotFoundException e) {
e.printStackTrace();
}
}

}
 
C

Chris Uppal

Marcin said:
Some people believe so. Others claim that the cure is worse than the
disease. I think it's not worth the trouble.

I agree with both those assertions and (fwiw) I largely share your opinion.

They way the system has been set up, a throws clause is a promise /not/ to
throw any (checked) exception which is not on the list. There is some value in
that, but it also puts a burden on the designer of the superclass to anticipate
all possible subclasses (or else s/he is making a promise that cannot or should
not be kept). Another way of looking at it is that it couples the design of
the superclass to its subclasses.

For instance a file-backed implementation of java.lang.List cannot throw
IOExceptions relating to failed filesystem operations (no such file, wrong
permissions, etc). So the implementer is forced into one of several unwelcome
options. One is just to swallow the errors. Not a good idea ;-) Another is
to wrap them in unchecked exceptions -- but if we have to do that often then
clearly the idea of having checked exceptions wasn't too hot in the first
place. Perhaps the best option is for the "standard" List operations to throw
unchecked exceptions, but for there to be a different interface which throws
checked exceptions -- thus clients which "know" that the List is actually a
DiskBackedList will be able to use the alternate API and thus be alerted to the
possibility of important failures.

Which suggests that a better scheme for checked exceptions would have been to
allow a method to override another method and declare that it throws /more/
exceptions than the parent. Code which handled the object via a reference of
the superclass type would not "know" about the extra possible exceptions -- and
thus would treat them as if they were unchecked. But code which handled the
object via a reference of the subclass type /would/ see the extended
definition, and thus the compiler could enforce checking in just the way it
does in real Java.

Incidentally, all this enforced checking happens only in the compiler, so it
would be possible to change or relax the rules without changing the JVM spec
(very unwelcome); or to use different rules in a Java-like language which
targeted the same JVM.

-- chris
 
A

Andy Dingley

/why/ force the programmer to declare what a method /may/ throw?

I see this as being related to the logic behind the "structured
programming" movement. (ask your Grandad, it pre-dated OOP) and the
whole "GOTOs considered harmful" concept.

The idea was that big programs were too complex to understand, so that
instead you'd break them into smaller programs that could be treated
as black boxes, and this required that such boxes could _only_ route
program flow along a few well-defined pathways - ideally one way in,
one way out. The enemy of the day was "spaghetti code" where GOTO
could jump from any location to any other location.

Requiring the thrown exceptions to be explicitly listed (along with
the normal end of block execution path) will list the set of all
possible output routes from a block of code. This allows some black-
box analysis of the overall program, without worrying too much about
what happens inside the box.
 
A

Arthur J. O'Dwyer

I have a number of books on Java but none seem to answer the
fundamental question on throws clauses: /why/ force the programmer to
declare what a method /may/ throw?

To quote one: "If you write a method that throws an exception, then
the Java compiler wil require that you do one of two things: you must
either declare that the method throws the exception using the 'throws'
keyword, or else you must provide a catch exception handler to catch
that exception." - Java Programming Explorer.

Is there really value in Java's behaviour? Or is this an unnecessary
burden on the programmer? Why not simply accept exceptions can be
thrown which are not listed, and deal with them in the innermost catch
clause which matches?

You probably know that C++ treats exceptions that way, although I
think they just did it that way so that you could mix old exceptionless
(unexceptional?) code with new exception-throwing code.
Also, if you make exception specifications part of the function's
"prototype", it would be one more thing to annoy programmers who use
function pointers. It's bad enough that I can't pass 'strcmp' as the
fourth argument to 'qsort'; now you're telling me I can't even pass
'int blah(const void *, const void *) throws(Bletch)' because its
exception specification doesn't match what 'qsort' expects?

But those considerations are all due to C++'s legacy from C, and I
don't think they apply to Java except insofar as a lot of Java programmers
came over from C and C++ in the early days. Exception handling is still
arcane enough that "because C++ does it that way" isn't a winning
argument, the way it is with operator precedence or control structures. :)


So, what are the benefits of explicitly declaring exception
specifications? First, psychological reasons; I recommend that
everyone read Tom Cargill's "Exception Handling: A false sense of
security", from 1994, which demonstrates how easy it is to forget
what you're doing when you write code involving exceptions.
http://www.awprofessional.com/content/images/020163371x/supplements/Exception_Handling_Article.html
Clearly, having to write exception specifications for all his member
functions would not have saved Reed; but might it have made him
think a little bit harder, and maybe eliminated one or two bugs
out of a dozen?

Second, there might be technical reasons to prefer that functions
declare anything they expect to catch, throw, or pass through.
Obviously we don't /need/ that information, because if we did, it would
be impossible to compile C++! :) But it might help. See this snippet
of "exception-handling" code in C, which needs the concept of a "rethrow"
clause to be implementable in ISO standard C:

http://en.wikipedia.org/wiki/Talk:Setjmp.h#Nested_catch_blocks

So if you're translating Java to ISO C, it might be useful. ;)

[...]
In terms of code maintenance, if dd could once throw a given exception
but now, because the relevant code has been removed, it cannot does cc
still need to provide for handling the exception simply /because/ it
is listed in dd's throws clause?

In terms of code maintenance, heck yes! If the programmer has removed
DD's ability to throw E, he should also have changed DD's exception
specification. If the compiler can warn him about his oversight, that's
a good thing in my book.

-Arthur
 
R

Robbert Haarman

There are always unchecked exceptions. But Alex is right, in many cases you
need or want to make the exception part of a method's interface, and
declaring it so is no different than declaring that a method, say, takes a
String arg. Does declaring method parameters place an "undue" burden on a
programmer?

That's another good question. My answer would be that it depends on what
you are doing. If your method only deals with parameters of one type, or
if it should accept values of any type that is compatible with what the
method does (e.g. the other methods it calls), then, yes, I would call
mandatory signature declarations an unnecessary burden. If, on the other
hand, you want to specialize methods on the types of their arguments
(polymorphism), then specifying those types is, obviously, necessary.
If you as a programmer really don't see the value of declaring exceptions,
don't throw them, or rely entirely on unchecked exceptions. After a while
you will discover scenarios where dealing with the results thereby is
actually harder than dealing with checked exceptions.

I don't believe so. Just because you do not explicitly list the
exceptions a method can throw does not mean that this information is not
available. You can always use (static) analysis to determine which
exceptions a method could throw.

You can always implement your own system to enforce that certain
exceptions are caught or otherwise handled if this proves beneficial in
your case. It does not have to be hard-coded in the language, forcing
the burden on situations where the benefits are not as great.
The burden isn't in the declaration of exceptions. Au contraire, exceptions
help you reduce the burden of dealing with all the corner cases.

I agree with the second part, but I don't agree that Java's take on
exceptions is not a burden. Often, the right thing to do with an
exception is to propagate it up the call stack. Often, the right
response to an error is to abort the process with an error message. In
many languages, these are the default behaviors, and you get them for
free.

In Java, you are required to write an exception handler if you want a
checked exception to abort the program with an error message. You also
have to add checked exceptions to throws clauses if you do not want to
handle them locally, but want them to propagate up the call stack. These
are arguably unnecessary efforts that have to be made not once, but
after certain refactorings, as well.

The rationale for Java's explicitness and mandatory declarations is that
they make programs more robust. They have that potential, but the extra
burden they impose on programmers also means there is less time to
perform other actions that make programs more robust. Also, it is not
uncommon for programmers to specify Object as an argument type or
Exception as an exception type, avoiding some of the burden Java imposes
on them, but simultaneously throwing away the benefits of the system.
Still, it demonstrates that, sometimes, Java's way of doing things is
percieved as less than desirable.

To conclude, my stance on the issue is that since (1) Java's explicit
approach is not almays desirable and (2) the benefits of Java's approach
can be obtained without mandating declarations in all circumstances and
hardcoding checked exceptions into the language, Java does impose an
unnecessary burden on programmers, and there are better approaches than
those Java takes.

Regards,

Bob
 
A

Alex Hunsley

Simon said:
It will, since overriding methods in subclasses could throw the exception. One
thing that does not compile is catching an exception that is not thrown. The
first method compiles, the second does not:
> public class Test {
>
> public void testThrows() throws java.io.FileNotFoundException {
> }
>
> public void testCatch() {
> try {
> System.out.println("Test");
> } catch (java.io.FileNotFoundException e) {
> e.printStackTrace();
> }
> }
>
> }


D'oh! Of course, you're right. I was think of exactly what you detail in
your example there (trying to catch an unthrown exception) and got it
the wrong way round!
thanks,
lex
 
A

Alex Hunsley

Chris said:
I agree with both those assertions and (fwiw) I largely share your opinion.

They way the system has been set up, a throws clause is a promise /not/ to
throw any (checked) exception which is not on the list. There is some value in
that, but it also puts a burden on the designer of the superclass to anticipate
all possible subclasses (or else s/he is making a promise that cannot or should
not be kept). Another way of looking at it is that it couples the design of
the superclass to its subclasses.

For instance a file-backed implementation of java.lang.List cannot throw
IOExceptions relating to failed filesystem operations (no such file, wrong
permissions, etc). So the implementer is forced into one of several unwelcome
options. One is just to swallow the errors. Not a good idea ;-) Another is
to wrap them in unchecked exceptions -- but if we have to do that often then
clearly the idea of having checked exceptions wasn't too hot in the first
place.

Another possibility is throwing a wrapped checked exception. Obviously,
that would expose some details of what was going on in the
implementation, but at least the code handling the wrapped exception
knows that something went wrong, and has access to the actual 'cause'
exception inside, if it is interested....
 
B

Bent C Dalager

There are always unchecked exceptions. But Alex is right, in many cases you
need or want to make the exception part of a method's interface, and declaring
it so is no different than declaring that a method, say, takes a String arg.
Does declaring method parameters place an "undue" burden on a programmer?

Well, you could always just write
public void foo(Object... args)
to get rid of that particular burden :)

And, of course,
public void foo(Object... args) throws Throwable
may be just the silver bullet we are looking for . . .

Cheers
Bent D
 
B

Bent C Dalager

Marcin 'Qrczak' Kowalczyk wrote:

They way the system has been set up, a throws clause is a promise /not/ to
throw any (checked) exception which is not on the list. There is some value in
that, but it also puts a burden on the designer of the superclass to anticipate
all possible subclasses (or else s/he is making a promise that cannot or should
not be kept). Another way of looking at it is that it couples the design of
the superclass to its subclasses.

I would rather think that the superclass designer should only look at
what sort of conceptual problems could arise from the superclass and
introduce checked exceptions for those. Implementors of subclasses
will then be left with the task of mapping their lower-level problems
to the superclass' higher-level concepts but this shouldn't be too
unproblematic.

A general List specification may be declared to throw
CouldNotInsertExceptions and
ElementUnreadableExceptions. Implementations of the list will know to
map their IOExceptions or whatever to those higher-level conditions.

Of course, having all methods in the superclass throwing some sort of
checked exception can get tedious for clients of that class.
For instance a file-backed implementation of java.lang.List cannot throw
IOExceptions relating to failed filesystem operations (no such file, wrong
permissions, etc). So the implementer is forced into one of several unwelcome
options. One is just to swallow the errors. Not a good idea ;-) Another is
to wrap them in unchecked exceptions -- but if we have to do that often then
clearly the idea of having checked exceptions wasn't too hot in the first
place. Perhaps the best option is for the "standard" List operations to throw
unchecked exceptions, but for there to be a different interface which throws
checked exceptions -- thus clients which "know" that the List is actually a
DiskBackedList will be able to use the alternate API and thus be alerted to the
possibility of important failures.

We might have two different types of List: UnreliableList and
ReliableList. The former would potentially throw checked exceptions
from all its methods since it is expected that it may occassionally be
cut off from its data source (file-based, db-based, network-based,
etc.) whileas the latter would not as we expect any problems in
accessing its data would be sufficiently catastrophic to grind the
rest of the system to a halt anyway (RAM-based storage).

Cheers
Bent D
 
R

Robbert Haarman

And, of course,
public void foo(Object... args) throws Throwable
may be just the silver bullet we are looking for . . .

It is true that this removes the effort of changing the declaratiens in
response to refactoring, but Java will still impose burdens on the
programmer. First, they have to actually type "Object" before every
argument and "throws Throwable" after every method declaration.
Secondly, if you actually want to use these arguments and throwables,
you will have to downcast them to another class, because Object and
Throwable probably don't have the methods and fields you need. This
requires more typing. It also incurs a performance penalty, because the
casts will be checked at run time. In the end, you get the worst of
dynamic typing and the worst of mandatory declarations, all in one
program.

Regards,

Bob
 
L

Lew

Robbert said:
In the end, you get the worst of
dynamic typing and the worst of mandatory declarations, all in one
program.

Which is why newsgroup readers have to watch out for humorous replies, and not
try actually to implement them.

- Lew
 
R

Robbert Haarman

Which is why newsgroup readers have to watch out for humorous replies, and
not try actually to implement them.

Point taken.

On the other hand, people do actually write code like that. Well.
perhaps not "throws Throwable", but certainly "throws Exception". And
anyone who has been programming in Java for some time will have seen the
likes of

void add(Object o) ...

and

((SomeClass) someContainer.get(someKey)).someMethod(...)

It's not just misguided programmers that write such code; Java was full
of it before the type system overhaul.

Regards,

Bob
 
N

nukleus

I agree with this position with both hands.

Exceptions can simplify the code but they have to be
fine grained and handles properly at each level.
By declaring that some method throws exception,
you force the upper layers of your code to handle
that exception, and you can, if you wish, handle
the same exception in that same method to display
some error message of final granularity, and then
rethrow that same exception to the higher level
to see if some operation can be retried or do all
the other things they do on the higher level.

Once i started introducing the exception mechanism
in some of old java code, i was unable to compile
the app for days, because of compiler errors
"exception is not handled".
But the more i worked on it, the better the end
results were.

Right now, the program handles just about any
error condition imaginable and displays/logs
the error messages of the most precise description,
plus, on the top of it, it can retry just about
any error condition imaginable, upto simply
turning the power switch off or unplugging your
network cable. As soon, as you plug it back in,
the program will try to redo the LAST operation
it was doing, without restarting the whole cycle
from the top.

Throws clause is probably one of the better benefits
of the entire exception mechanism. Yes, it forces you
to hancle those exceptions on the higher level,
but it all happens during the compile time, preventing
all sorts of error conditions imaginable in the run time.

The only thing i suggest in terms of using exceptions
mechanism in general, is to use fine enough granularity
exceptions and handle them on the most appropriate level.
This way, you have about the most powerful structure,
allowing you to display the error messages of the most
precistion and, at the same tim, try to recover just
about any operation imaginable.

The extreme case: use the exceptions everywhere,
is simply foolish. First of all, you need to understand
the very nature of exceptions and it is essentially
an equivalent of a goto statement, that totally screws
up your stack and throws your program into place
you could not have imagine.

Any program is a fine mix of conditional code
and exceptions. Yes, purely conditional code approach
is way too messy as just after about any call,
you'd have to check on results of it. Otherwise,
you can not be certain that you can proceed,
which ends up in large amounts of unnecessary code
compared to properly used exceptions.

But...

You can't just get away from conditional code,
because it is the VERY NATURE of a program,
which is logic, and logic, its turn, is a conglomeration
of ifs and buts.

If you don't have those ifs and buts,
you'll end up with the most rigid and inflexible,
totalitarian system, forcing users to do things
he could not have imagine in his wildest dreams
he has to EVER do, going to the point of obscene
by requiring you to reboot your box in some extreme
instances.

Just because those masters of delusion
do not think that using fine grained ifs and buts
you'll achieve the most flexible and most pleasant
and comfortable environment for the USER,
which is what your entire app is there for on the
first place.
Well, you could always just write
public void foo(Object... args)
to get rid of that particular burden :)

Is it supposed to be a joke?
And, of course,
public void foo(Object... args) throws Throwable
may be just the silver bullet we are looking for . . .

Just about the most foolish thing to do.
Once you make the declaration of this type,
you loose ALL the granularity of error condition
handling, and will NEVER be able to recover from
errors and attempt to redo only starting from
the error state in your program and continue on
after error condition has been removed,
which is what just about all real life apps do.

I have over a dozen of custom exceptions
derived at the most fine grained level of
standard exceptions, and handle all the exceptions
starting from the most fine grained
and going upto Exception level itself,
which means that you basically have a fatal error.
It means you have an error condition that was not
handled by ANY of your mechanism. It could be just
about the most innocent operation such as division
by 0, which could be properly handled and the
default values substituted for the error values
and the program could keep crunching along.

Summary:
You create a number of custom exceptions with fine
enough granularity to report/handle just about
ANY error condition imaginable, even in your wildest
dreams, and giving you the most precise scope of
error condition that could be handle locally
to provide the most precise and detailed error
messages or attempt to recover.

You HANDLE those exceptions starting from the most
fine grained level. You may have several levels
of catch blocks and act upon different exceptions
differently. In some cases, the operation could
be recovered by simply restarting the local level
of it. In other cases, the result could be more
brutal, in cases where you lost connection to the net,
or things like that, in which case you'd have to
retry going from higher levels,
and in some cases, those errors could be equivalent
to fatal, in which case you have to decide to either
stop the program completely, close some frames,
or restart it from the top.

"THATs the way you do it".

Good luck.
 
N

nukleus

Robbert Haarman said:
It is true that this removes the effort of changing the declaratiens in
response to refactoring, but Java will still impose burdens on the
programmer. First, they have to actually type "Object" before every
argument and "throws Throwable" after every method declaration.

throws Throwable means your very design is incorrect
as you are entirely missing the granularity of your errors.
You'll never be able to successfuly recover
and forget about reattempting the operation
on a fine grained level as you will be thrown
several layers up.
Secondly, if you actually want to use these arguments and throwables,
you will have to downcast them to another class, because Object and
Throwable probably don't have the methods and fields you need. This
requires more typing. It also incurs a performance penalty, because the
casts will be checked at run time. In the end, you get the worst of
dynamic typing and the worst of mandatory declarations, all in one
program.

A BASIC rule in object oriented programming:

DO NOT USE CASTS.
Unless it is absolutely unavoidable.
 
N

nukleus

Lew said:
Which is why newsgroup readers have to watch out for humorous replies, and not
try actually to implement them.

I agree with both hands.
These jokers are nothing more than noise makers,
having nothing to contribute of just about ANY value
beyond their "smart ass" image.

And on a technical group, it isn't even laughable.
 
N

nukleus

Robbert Haarman said:
Point taken.

On the other hand, people do actually write code like that. Well.
perhaps not "throws Throwable", but certainly "throws Exception".

Not much different from throws Throwable
as you loose any and all the precision.
Basically, your program is screwed beyond repair
and the entire exceptions mechanism needs to be redesigned.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top